Compare commits
	
		
			390 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fff16e6650 | |||
| d6f6d10cb6 | |||
| 1249904582 | |||
| 8e0437728b | |||
| fb0c0718e4 | |||
| aa8196db3a | |||
| b2223b5110 | |||
| 3837c5673a | |||
| 910a0860a0 | |||
| 009431fb98 | |||
| 579ece6256 | |||
| 822e97d3c3 | |||
| e0ae2ec42b | |||
| 859f6e2567 | |||
| 120d3c9dc8 | |||
| 342a95ddbe | |||
| 0b6134dd80 | |||
| e76854c23b | |||
| 24b98983cf | |||
| 3945f3cf38 | |||
| 5d7eb690e4 | |||
| bef180f4ba | |||
| e76e5abcf8 | |||
| 418f9c2662 | |||
| 716b72880a | |||
| 5de0fd792f | |||
| 13b557aba8 | |||
| 34dfc9add6 | |||
| 3a4575f251 | |||
| 178c2579d5 | |||
| 50be992b72 | |||
| d00f46eee1 | |||
| 44d6cba403 | |||
| 37bc5ff17b | |||
| e459bb04cc | |||
| 01eba88adf | |||
| 016fba5279 | |||
| da5d62ae03 | |||
| eb46c5eea6 | |||
| 100094e803 | |||
| e723cbf3b3 | |||
| 00d16ef59f | |||
| 5204ba5e24 | |||
| 629aabd3a3 | |||
| 1b9b9ed372 | |||
| b4e7f9046c | |||
| f09224d5c0 | |||
| 5f6ee33e2b | |||
| 635e2ba0e0 | |||
| 6109996ade | |||
| e4b80c9ab3 | |||
| 7521ad8bbb | |||
| b994065e18 | |||
| 5b1b84caff | |||
| d28a0e1dbb | |||
| 94d52df322 | |||
| 0ae3d36f0c | |||
| a7fb2b8a1a | |||
| 16d0dbab5b | |||
| 99c3050411 | |||
| 937265e828 | |||
| 60cd52a959 | |||
| b009501a53 | |||
| ee49e78dcd | |||
| 60aa919b14 | |||
| 8252a35771 | |||
| fc668c6880 | |||
| bb7f2a611a | |||
| c575c73764 | |||
| bd3ea721c3 | |||
| 82423ec467 | |||
| 64311e9652 | |||
| 77662b9c19 | |||
| b1031e3115 | |||
| 64c96f25d4 | |||
| 5ad42d6ca7 | |||
| 0386d4e88a | |||
| cda4512822 | |||
| eb6af4b4f0 | |||
| 4e51b128e6 | |||
| 0277263f98 | |||
| 99fb420d58 | |||
| a45c5da0a7 | |||
| ff1bc8a44a | |||
| 284bdc6e33 | |||
| 107360cd93 | |||
| abf9aa475b | |||
| 6156e04eb3 | |||
| b541c93797 | |||
| 6fd6413d6f | |||
| dfa38d3421 | |||
| 6e04b71c1a | |||
| 5afa541b30 | |||
| 9e5a093a3a | |||
| 2cf8e0291a | |||
| 53aa3bc3ae | |||
| 1ada5d9c2c | |||
| eb0910be57 | |||
| e9d5527482 | |||
| e6df764562 | |||
| 915bbbbde0 | |||
| f67e089ff3 | |||
| b841cc8b95 | |||
| 5f4b4baadb | |||
| 94b5a54655 | |||
| 95eb8b6ae4 | |||
| 8acbfa8967 | |||
| 1a115a8423 | |||
| 5a2172bb9b | |||
| 6b590671bc | |||
| cee1ab1347 | |||
| 9d0c6b9ef4 | |||
| 0e682bf630 | |||
| 8b95b300e2 | |||
| e1bd364278 | |||
| 7edc3427e1 | |||
| d3a3de2eac | |||
| bc2a8caf3e | |||
| d00446dc7b | |||
| 40ada1c31e | |||
| f24b2b9b4c | |||
| 2a644d7070 | |||
| ee0c1496e6 | |||
| 1da15783d5 | |||
| 6a925cb27f | |||
| 88ad64f113 | |||
| 1bc840430f | |||
| 76be8d5a87 | |||
| 8c4a54eb07 | |||
| dab5bee3c0 | |||
| bc8548fa1e | |||
| 476f919121 | |||
| 1c330d0301 | |||
| 47f0cd0b58 | |||
| f97be4e729 | |||
| 48b8dfe973 | |||
| 64b6c4d5f7 | |||
| fe16c66cf2 | |||
| 8d8695ba13 | |||
| e296256332 | |||
| 65111e87c1 | |||
| 95b1490f84 | |||
| 27a1f57ed3 | |||
| c6db6c5535 | |||
| bd22d3be36 | |||
| ba9d4587cb | |||
| 74c042a86b | |||
| 95fcd1dcc4 | |||
| 2de861d4c1 | |||
| e6d80c8ccb | |||
| 1aa2b3b065 | |||
| 88566719ec | |||
| 870e772da2 | |||
| e8e3ddceff | |||
| a5d1b76891 | |||
| e93f4e99f9 | |||
| 50aa891709 | |||
| 4b47e70b13 | |||
| c4acf774ec | |||
| 7ff1d50079 | |||
| 258b3cea66 | |||
| a3daa2d24f | |||
| 9f754ef0e9 | |||
| 773fbfc579 | |||
| 85fa9d942e | |||
| 83e782c7c5 | |||
| 9ee768551f | |||
| e45f8fa9ef | |||
| 891ea2da12 | |||
| 5e417f0714 | |||
| c53b579fca | |||
| ca9c390bb2 | |||
| 7d654f4a20 | |||
| b810bb01db | |||
| c2bd696bfe | |||
| 9fec315910 | |||
| 9a8a978e49 | |||
| e24b84e709 | |||
| 305b18ef57 | |||
| 22e9f53c42 | |||
| 6870e31a81 | |||
| e07d1e42e2 | |||
| c89caf7855 | |||
| 3b7c25b106 | |||
| 6079e1fa90 | |||
| 434466b306 | |||
| e1ac35f848 | |||
| 5994b22464 | |||
| bfc93158f5 | |||
| 29f99f0b20 | |||
| e4872131c8 | |||
| 16e1434f2a | |||
| 842badfa69 | |||
| 8ebc88aebb | |||
| c111ec9d91 | |||
| e85cdaf324 | |||
| 599d340a72 | |||
| 89b7fb8072 | |||
| dcaca2ecbd | |||
| b8725c96cd | |||
| 36930259d2 | |||
| 4397566f1e | |||
| 7c324869a4 | |||
| 7e80608066 | |||
| af7e44cf7c | |||
| 05099d066b | |||
| 937486a66b | |||
| e8de1f6d9c | |||
| cd9a5469fd | |||
| 1124f25ea3 | |||
| a79a87de4c | |||
| d2193bf428 | |||
| d9eab9f254 | |||
| 7d08ea8466 | |||
| ba1eb2fa73 | |||
| 4dbca6096f | |||
| 03be2d0492 | |||
| b0aca9de13 | |||
| 019a0297a9 | |||
| 0f93febd86 | |||
| d6c96b781f | |||
| 918bb94644 | |||
| a8774fa524 | |||
| e4b908ecde | |||
| 247ba40309 | |||
| fcf01ba677 | |||
| fb5a64c251 | |||
| 3d51ba0dc2 | |||
| 07636f51c4 | |||
| eff2050959 | |||
| c96a21cf99 | |||
| d4d847059a | |||
| 880d722912 | |||
| 1ef1053d3f | |||
| fa522a85d6 | |||
| f09e58c69c | |||
| 63e02492e8 | |||
| fd406eb3e6 | |||
| 88ade26ef7 | |||
| 3aea259e41 | |||
| 0f64767437 | |||
| d2430badbe | |||
| a880ed2b18 | |||
| e0f0fa9a2a | |||
| 5d2025aa43 | |||
| 8b7f5a765b | |||
| 9cd94004fc | |||
| 8042bca7cc | |||
| 8d89d158d1 | |||
| ccacdf274b | |||
| f1ceef05fc | |||
| c5697242ee | |||
| d0a48ab94b | |||
| 2b037d41ac | |||
| b7d38dd849 | |||
| 02d24139e9 | |||
| ad638e8bc8 | |||
| 76cb20debe | |||
| 3e9383e6d9 | |||
| aec8bf56a2 | |||
| 18335e3325 | |||
| bca9605d4a | |||
| 0321f0e979 | |||
| f97c2a36f6 | |||
| 5d945f5bc5 | |||
| 1c4975589f | |||
| fffe5c2c4b | |||
| 3a57e1c766 | |||
| 8b70882fec | |||
| 7fb7ba0d2b | |||
| 78514c6572 | |||
| 19393006ef | |||
| cb704c4551 | |||
| 04a09c3ce5 | |||
| ca8f978667 | |||
| 0cc91ac037 | |||
| 1b6f86669c | |||
| 264868bb6a | |||
| 02087a541e | |||
| dee0e37a85 | |||
| ea7a4a560b | |||
| f63e17775c | |||
| c4240d36f7 | |||
| ed13a0d14b | |||
| 8fa0be7633 | |||
| a99c022608 | |||
| 12bcbd28f3 | |||
| 4ece21cdf2 | |||
| a7642c2da4 | |||
| 32024cf2c5 | |||
| ef373caba7 | |||
| 396bd22199 | |||
| 4bff50a088 | |||
| dbc0ab76af | |||
| 289a8c14d3 | |||
| 44ed633cbf | |||
| 0a55d73146 | |||
| 40dda1150c | |||
| 09b61ec684 | |||
| e53467da22 | |||
| 09d27c0b05 | |||
| 3b18be5874 | |||
| ff15308c03 | |||
| fa3dc870d3 | |||
| 5e6ada140c | |||
| e8f7c1c832 | |||
| 266a11f64f | |||
| e442b92a5f | |||
| b337873ca2 | |||
| 896fff04aa | |||
| 6d0bca6d67 | |||
| d53c1ae2bd | |||
| afd73d53be | |||
| 652e55e80e | |||
| d67dfdf2e7 | |||
| 51f2d96ad6 | |||
| 30867b4ba1 | |||
| ec8d946a41 | |||
| 1eea935207 | |||
| 616990b930 | |||
| e5c31c9dd4 | |||
| 4f3f7d1edb | |||
| 86f13003b5 | |||
| 6d2431b683 | |||
| 57e17f2864 | |||
| bcc7d7770e | |||
| 89ea40d7f3 | |||
| 56b5008278 | |||
| 555778fca4 | |||
| ec1a6226a9 | |||
| 3c541ada89 | |||
| 5f677e71e9 | |||
| 5b3e66c4f6 | |||
| 9b0252fb75 | |||
| 2e3750c87c | |||
| 377d691053 | |||
| e90fe73aa2 | |||
| 3baa681c2b | |||
| a588bc4631 | |||
| b195c707b0 | |||
| 25ac84e5fd | |||
| 5e9df32bfa | |||
| 722feac8bd | |||
| 4be87a64b9 | |||
| 505ca6a58e | |||
| 5f1c8f3627 | |||
| 3834079481 | |||
| 9faa93e292 | |||
| 27609dc5e0 | |||
| 2b57d49e4e | |||
| dc0c738471 | |||
| ca41f4d4f2 | |||
| e1427f3ecb | |||
| e2fb9a66ad | |||
| 7278648642 | |||
| a4c955ce85 | |||
| f086027910 | |||
| 543b3bd937 | |||
| c0534a3b06 | |||
| 0c9785af36 | |||
| 482149139a | |||
| c89038f337 | |||
| 696d3ffabf | |||
| 14e5d0e740 | |||
| e4ae1dd475 | |||
| 46cd262fab | |||
| 3f5418083f | |||
| 1586c2f9e6 | |||
| e64b318a42 | |||
| 2033572c83 | |||
| ce678c1b76 | |||
| 83495b101c | |||
| 6c2a5f904d | |||
| c1251d3332 | |||
| 4ef1b7abe8 | |||
| d822e4ab3f | |||
| b01fe050d2 | |||
| 1a4cf211eb | |||
| 4541304fa8 | |||
| 894160f3f7 | |||
| 6a91bd53e2 | |||
| 0f013304ef | |||
| 6f4f4ccb16 | |||
| 7138ca1f5f | |||
| 45e7f6a0d1 | |||
| a7098df9cf | |||
| 054c7faaac | |||
| c3a4c659c0 | |||
| eddfeb10a5 | |||
| 0361f8ad69 | 
							
								
								
									
										55
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						| @@ -1,6 +1,27 @@ | |||||||
|  | --- | ||||||
|  | kind: secret | ||||||
|  | name: docker_username | ||||||
|  | get: | ||||||
|  |   path: odit-registry-builder | ||||||
|  |   name: username | ||||||
|  |  | ||||||
|  | --- | ||||||
|  | kind: secret | ||||||
|  | name: docker_password | ||||||
|  | get: | ||||||
|  |   path: odit-registry-builder | ||||||
|  |   name: password | ||||||
|  |  | ||||||
|  | --- | ||||||
|  | kind: secret | ||||||
|  | name: git_ssh | ||||||
|  | get: | ||||||
|  |   path: odit-git-bot | ||||||
|  |   name: sshkey | ||||||
|  |  | ||||||
| --- | --- | ||||||
| kind: pipeline | kind: pipeline | ||||||
| type: docker | type: kubernetes | ||||||
| name: build:dev | name: build:dev | ||||||
|  |  | ||||||
| steps: | steps: | ||||||
| @@ -20,22 +41,44 @@ steps: | |||||||
|       author_email: bot@odit.services |       author_email: bot@odit.services | ||||||
|       remote: git@git.odit.services:lfk/frontend.git |       remote: git@git.odit.services:lfk/frontend.git | ||||||
|       ssh_key: |       ssh_key: | ||||||
|         from_secret: GITLAB_SSHKEY |         from_secret: git_ssh | ||||||
|   - name: build dev |   - name: build dev | ||||||
|     image: plugins/docker |     image: plugins/docker | ||||||
|     depends_on: [clone] |     depends_on: [clone] | ||||||
|     settings: |     settings: | ||||||
|       username: |       username: | ||||||
|         from_secret: DOCKER_REGISTRY_USER |         from_secret: docker_username | ||||||
|       password: |       password: | ||||||
|         from_secret: DOCKER_REGISTRY_PASSWORD |         from_secret: docker_password | ||||||
|       repo: registry.odit.services/lfk/frontend |       repo: registry.odit.services/lfk/frontend | ||||||
|       tags: |       tags: | ||||||
|         - dev |         - dev | ||||||
|       registry: registry.odit.services |       registry: registry.odit.services | ||||||
|  |       mtu: 1000 | ||||||
| trigger: | trigger: | ||||||
|   branch: |   branch: | ||||||
|     - dev |     - dev | ||||||
|   event: |   event: | ||||||
|     - push |     - push | ||||||
|  |  | ||||||
|  | --- | ||||||
|  | kind: pipeline | ||||||
|  | type: kubernetes | ||||||
|  | name: build:tags | ||||||
|  | steps: | ||||||
|  |   - name: build $DRONE_TAG | ||||||
|  |     image: plugins/docker | ||||||
|  |     depends_on: [clone] | ||||||
|  |     settings: | ||||||
|  |       username: | ||||||
|  |         from_secret: docker_username | ||||||
|  |       password: | ||||||
|  |         from_secret: docker_password | ||||||
|  |       repo: registry.odit.services/lfk/frontend | ||||||
|  |       tags: | ||||||
|  |         - '${DRONE_TAG}' | ||||||
|  |       registry: registry.odit.services | ||||||
|  |       mtu: 1000 | ||||||
|  | trigger: | ||||||
|  |   event: | ||||||
|  |   - tag | ||||||
							
								
								
									
										7
									
								
								.vscode/i18n-ally-custom-framework.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | |||||||
|  | languageIds: | ||||||
|  |     - javascript | ||||||
|  |     - svelte | ||||||
|  |     - html | ||||||
|  | monopoly: false | ||||||
|  | refactorTemplates: | ||||||
|  |     - "{$_('$1')}" | ||||||
							
								
								
									
										386
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						| @@ -2,8 +2,393 @@ | |||||||
|  |  | ||||||
| All notable changes to this project will be documented in this file. Dates are displayed in UTC. | All notable changes to this project will be documented in this file. Dates are displayed in UTC. | ||||||
|  |  | ||||||
|  | #### [0.8.3](https://git.odit.services/lfk/frontend/compare/0.8.2...0.8.3) | ||||||
|  |  | ||||||
|  | - Sorted translation 🌍 [`d6f6d10`](https://git.odit.services/lfk/frontend/commit/d6f6d10cb6b639a1f988e0da4811355750b0f027) | ||||||
|  | - Smaller bugfixes [`8e04377`](https://git.odit.services/lfk/frontend/commit/8e0437728bd04223a23cdf1879c6c739ca8ebef7) | ||||||
|  | - More small fixes [`1249904`](https://git.odit.services/lfk/frontend/commit/12499045824c13a3ee35c6cc8c3c3a3130dbec12) | ||||||
|  |  | ||||||
|  | #### [0.8.2](https://git.odit.services/lfk/frontend/compare/0.8.1...0.8.2) | ||||||
|  |  | ||||||
|  | > 20 March 2021 | ||||||
|  |  | ||||||
|  | - Now using env base url [`aa8196d`](https://git.odit.services/lfk/frontend/commit/aa8196db3a9ff1bcd2b5f0ce655957bfa4e310ab) | ||||||
|  | - 🚀RELEASE v0.8.2 [`fb0c071`](https://git.odit.services/lfk/frontend/commit/fb0c0718e405312f239bc3829b3d9c203d246458) | ||||||
|  |  | ||||||
|  | #### [0.8.1](https://git.odit.services/lfk/frontend/compare/0.8.0...0.8.1) | ||||||
|  |  | ||||||
|  | > 20 March 2021 | ||||||
|  |  | ||||||
|  | - ⚡ CI - add build:tags pipeline [`910a086`](https://git.odit.services/lfk/frontend/commit/910a0860a0bd240c5ff9d23dc33923f941b1e10b) | ||||||
|  | - 🚀RELEASE v0.8.1 [`b2223b5`](https://git.odit.services/lfk/frontend/commit/b2223b5110cc31740e69de66c74ab3f83a046308) | ||||||
|  |  | ||||||
|  | #### [0.8.0](https://git.odit.services/lfk/frontend/compare/0.7.0...0.8.0) | ||||||
|  |  | ||||||
|  | > 20 March 2021 | ||||||
|  |  | ||||||
|  | - 🚀RELEASE v0.8.0 [`009431f`](https://git.odit.services/lfk/frontend/commit/009431fb98bc84c71590644a5d3606805d432202) | ||||||
|  | - new license file version [CI SKIP] [`579ece6`](https://git.odit.services/lfk/frontend/commit/579ece625651c8bf37b5704e82264a8833811f68) | ||||||
|  | - Merge pull request 'User settings feature/103-settings_page' (#104) from feature/103-settings_page into dev [`822e97d`](https://git.odit.services/lfk/frontend/commit/822e97d3c35be57ecf9ed6bd6af474fd89e7d8d8) | ||||||
|  | - Sorted translations🌍 [`120d3c9`](https://git.odit.services/lfk/frontend/commit/120d3c9dc81a0878d0dd64b66caadc6201651bda) | ||||||
|  | - Translated headers [`01eba88`](https://git.odit.services/lfk/frontend/commit/01eba88adfd7cced75e828427494cb99fdf01d70) | ||||||
|  | - Implemented change detection [`37bc5ff`](https://git.odit.services/lfk/frontend/commit/37bc5ff17b8f97dfc76962435f9cb64156848c82) | ||||||
|  | - Added new profile deletion modal [`13b557a`](https://git.odit.services/lfk/frontend/commit/13b557aba89378af40e2f9feb5f90a1c582aea91) | ||||||
|  | - Implemented the password change logic [`24b9898`](https://git.odit.services/lfk/frontend/commit/24b98983cf964fe2b5594e20b22c63e97ada3166) | ||||||
|  | - The settings page now boasts your profile picture [`e459bb0`](https://git.odit.services/lfk/frontend/commit/e459bb04cc3fcdc5d176e53b764ad3e995d66762) | ||||||
|  | - Added inputs for password update [`5d7eb69`](https://git.odit.services/lfk/frontend/commit/5d7eb690e4f2be0d389c785abe73c137cf25c29c) | ||||||
|  | - Added delete Profile button [`418f9c2`](https://git.odit.services/lfk/frontend/commit/418f9c2662531b997699e8db05e21da394025688) | ||||||
|  | - Implemented profile updates [`3a4575f`](https://git.odit.services/lfk/frontend/commit/3a4575f25148c74878c978e5cf9885ad12101f00) | ||||||
|  | - Added translations 🌍 [`3945f3c`](https://git.odit.services/lfk/frontend/commit/3945f3cf3862369ff179379d76090b1e2d78b417) | ||||||
|  | - Its translation time 🌍 [`5de0fd7`](https://git.odit.services/lfk/frontend/commit/5de0fd792f15241a4a49eda575bc8f23ba6c6974) | ||||||
|  | - Fixed delete_triggered not getting reset [`e76e5ab`](https://git.odit.services/lfk/frontend/commit/e76e5abcf87883602002fb5687a3fd7b95f24793) | ||||||
|  | - Added deletion confirmation modal [`716b728`](https://git.odit.services/lfk/frontend/commit/716b72880ad7468694affaf5549b2d34186294b6) | ||||||
|  | - Added translations [`e76854c`](https://git.odit.services/lfk/frontend/commit/e76854c23be9d909c35a61f8f8e65f743ccf3ec4) | ||||||
|  | - Added translations🌍 [`34dfc9a`](https://git.odit.services/lfk/frontend/commit/34dfc9add670040e2308964ed8c81d3a99f11f0d) | ||||||
|  | - Added hint to the logout after password update [`0b6134d`](https://git.odit.services/lfk/frontend/commit/0b6134dd8093427ec15e55d805dae3b18dc55c6d) | ||||||
|  | - Added missing translation [`44d6cba`](https://git.odit.services/lfk/frontend/commit/44d6cba403c117b1b76ecad32fbc4c2bd02a8cc3) | ||||||
|  | - Updated old endpoints [`178c257`](https://git.odit.services/lfk/frontend/commit/178c2579d56c3e78e947e658a5c3afe367c3e8ce) | ||||||
|  | - Settings - rouded corners on password change [`e0ae2ec`](https://git.odit.services/lfk/frontend/commit/e0ae2ec42b89271282e3e96e41de38afd08aa229) | ||||||
|  | - Now force reloading to log out [`342a95d`](https://git.odit.services/lfk/frontend/commit/342a95ddbeceac2cf3547462a9b1996ecd7dba61) | ||||||
|  | - Removed useless debug info 🐞 [`bef180f`](https://git.odit.services/lfk/frontend/commit/bef180f4ba7aaf252d87efc789eb53fb75abbdf1) | ||||||
|  | - Bumped lfk client js version [`50be992`](https://git.odit.services/lfk/frontend/commit/50be992b72dd71eb2c756b1bf6f2d606344fe7ca) | ||||||
|  | - Now showing logo as default profile pic [`d00f46e`](https://git.odit.services/lfk/frontend/commit/d00f46eee1fd28095064f50620c15ac648f331b9) | ||||||
|  | - Moved settings to their own folder [`016fba5`](https://git.odit.services/lfk/frontend/commit/016fba527910b95eb2690207bbede91004d2f2d9) | ||||||
|  | - Now also resetting postdata (prevent against password leaks) [`859f6e2`](https://git.odit.services/lfk/frontend/commit/859f6e25675362ee2ef5553255e3dc9e3040672c) | ||||||
|  |  | ||||||
|  | #### [0.7.0](https://git.odit.services/lfk/frontend/compare/0.6.0...0.7.0) | ||||||
|  |  | ||||||
|  | > 19 March 2021 | ||||||
|  |  | ||||||
|  | - Merge pull request 'feature/62-contract-generation' (#76) from feature/62-contract-generation into dev [`#62`](https://git.odit.services/lfk/frontend/issues/62) | ||||||
|  | - Merge pull request 'Small bugfixes - feature/64-dialog_clearing_bug' (#75) from feature/64-dialog_clearing_bug into dev [`#64`](https://git.odit.services/lfk/frontend/issues/64) | ||||||
|  | - Fixed non-automatic logout [`#38`](https://git.odit.services/lfk/frontend/issues/38) | ||||||
|  | - Merge pull request 'feature/69-translation-keys' (#74) from feature/69-translation-keys into dev [`#69`](https://git.odit.services/lfk/frontend/issues/69) | ||||||
|  | - Merge pull request 'feature/50-contact-management' (#67) from feature/50-contact-management into dev [`#50`](https://git.odit.services/lfk/frontend/issues/50) | ||||||
|  | - 🚀RELEASE v0.7.0 [`da5d62a`](https://git.odit.services/lfk/frontend/commit/da5d62ae03bbd057f52473ec047e09067e3715a3) | ||||||
|  | - Ordered locales [`0f93feb`](https://git.odit.services/lfk/frontend/commit/0f93febd866b0117d7bdf6149c2e8b94f801b1b5) | ||||||
|  | - Added missing translation [`918bb94`](https://git.odit.services/lfk/frontend/commit/918bb946446608ecebf3e388607068f7aa48e105) | ||||||
|  | - Sorted translations [`eff2050`](https://git.odit.services/lfk/frontend/commit/eff205095915e3f9bf9dd2f746e5245015740ca5) | ||||||
|  | - Sorted translations 🌎🌍 [`5f6ee33`](https://git.odit.services/lfk/frontend/commit/5f6ee33e2bcabc007bba340ef51bc28f707aa58d) | ||||||
|  | - Added language keys🌎 [`60aa919`](https://git.odit.services/lfk/frontend/commit/60aa919b141d26853300db28ab20a6fe41c3f0b6) | ||||||
|  | - Sorted translations 🌎 [`16d0dba`](https://git.odit.services/lfk/frontend/commit/16d0dbab5bb587cb859c30e04321fb5758455f80) | ||||||
|  | - Translated missing german stuff 🌍 [`e4b80c9`](https://git.odit.services/lfk/frontend/commit/e4b80c9ab34d31ec31df55904eacfac2c8728b10) | ||||||
|  | - Added translation keys [`880d722`](https://git.odit.services/lfk/frontend/commit/880d722912fbb8aa7b383f3a7f4e04b639287c3f) | ||||||
|  | - Added german translations [`d4d8470`](https://git.odit.services/lfk/frontend/commit/d4d847059af0a02a67e1759812b420f92609f517) | ||||||
|  | - Working add fixed/normal switch [`5d2025a`](https://git.odit.services/lfk/frontend/commit/5d2025aa43f6c4bc4c8f792be22fcec07edd37b9) | ||||||
|  | - Added basic donation detail [`88ade26`](https://git.odit.services/lfk/frontend/commit/88ade26ef7b112a777af2c7efc10088f79b8f04e) | ||||||
|  | - Added basic overview with emptystate [`f1ceef0`](https://git.odit.services/lfk/frontend/commit/f1ceef05fca603f76480df47623941a4ec6a18d1) | ||||||
|  | - Finished group creation modal [`d2193bf`](https://git.odit.services/lfk/frontend/commit/d2193bf428d021328f407e6be1e661b582fd2a1d) | ||||||
|  | - Added basic donation overview [`8d89d15`](https://git.odit.services/lfk/frontend/commit/8d89d158d11874369265ba668967762d4ee20dd8) | ||||||
|  | - Donors now get their donations linked in the donor detail [`3aea259`](https://git.odit.services/lfk/frontend/commit/3aea259e415e21f99602bc4e4bfc8cb633d68ab9) | ||||||
|  | - Fixed text size mismatch [`3d51ba0`](https://git.odit.services/lfk/frontend/commit/3d51ba0dc2da65e826bdbb29a6da11ba07078139) | ||||||
|  | - Applied the select fix to all things runner 🏃♀️🏃♂️ [`0386d4e`](https://git.odit.services/lfk/frontend/commit/0386d4e88ab57457fa981399627b2b61f38bb1ae) | ||||||
|  | - Now the saveing button even worx :O [`63e0249`](https://git.odit.services/lfk/frontend/commit/63e02492e81e3ee02386d3e4363d3ae20125aa8b) | ||||||
|  | - Switched import modal over to svelte select [`bd3ea72`](https://git.odit.services/lfk/frontend/commit/bd3ea721c301bf6ef67ef4e1e27ca73e799e1a35) | ||||||
|  | - Switched the scanstation detail over to svelte select👀👀 [`b1031e3`](https://git.odit.services/lfk/frontend/commit/b1031e3115ce03ee68e10f3240f8e829f3f7cfde) | ||||||
|  | - Switched the scanstation modal over to svelte select👀👀 [`64c96f2`](https://git.odit.services/lfk/frontend/commit/64c96f25d4a9277df431011f4660b59a2eb0a9da) | ||||||
|  | - Fixed select bug for sponsoring detail 🛠 [`64311e9`](https://git.odit.services/lfk/frontend/commit/64311e96528e0b023892170b5541652cc22f176b) | ||||||
|  | - Added missing translation keys [`c96a21c`](https://git.odit.services/lfk/frontend/commit/c96a21cf9990a80ec2571366a40116a9d5d64549) | ||||||
|  | - Fixed runner group update recognition being weired [`b009501`](https://git.odit.services/lfk/frontend/commit/b009501a533e6ebfa79dde4c48554c99ee53c9c2) | ||||||
|  | - Fixed runner group update recognition being weired [`ee49e78`](https://git.odit.services/lfk/frontend/commit/ee49e78dcd34b0d22c3e3c16ed8e0eb8805ea4c8) | ||||||
|  | - Donors now get their donations linked in the donor overview [`0f64767`](https://git.odit.services/lfk/frontend/commit/0f64767437626e90adaa931cb15c491e5bfa76c9) | ||||||
|  | - Fixed load order bug [`c575c73`](https://git.odit.services/lfk/frontend/commit/c575c7376499be07980cb4dbdb7a177e0a18e29c) | ||||||
|  | - Fixed select bug for sponsoring modal 🐞 [`77662b9`](https://git.odit.services/lfk/frontend/commit/77662b9c19bbef57c49471d9326b48656aebf1ff) | ||||||
|  | - Fixed select bug for org detail 🏠 [`82423ec`](https://git.odit.services/lfk/frontend/commit/82423ec46798a354e6f3208b67eac3723f874a4d) | ||||||
|  | - Added new icon for donations [`fa522a8`](https://git.odit.services/lfk/frontend/commit/fa522a85d6215d3f6dd9af05f18dd75fbfb5e0c4) | ||||||
|  | - Formatting [`f09e58c`](https://git.odit.services/lfk/frontend/commit/f09e58c69c4d279f825f756c789db08980be9435) | ||||||
|  | - Bumped non-svelte dev dependencies🔝 [`629aabd`](https://git.odit.services/lfk/frontend/commit/629aabd3a35766b181bebe28a31d531362c0a400) | ||||||
|  | - Added missing language keys [`6109996`](https://git.odit.services/lfk/frontend/commit/6109996adeec808847a72691a9cc3f174285612e) | ||||||
|  | - Added groupoverview to router [`d9eab9f`](https://git.odit.services/lfk/frontend/commit/d9eab9f2547744ae018f6bd20c730ffd289db4c0) | ||||||
|  | - Added the new, shiny badges to donor overview [`b0aca9d`](https://git.odit.services/lfk/frontend/commit/b0aca9de13b5dcfc082a0bd27bfec4a2ef4402cf) | ||||||
|  | - Fixed missing inversion [`a8774fa`](https://git.odit.services/lfk/frontend/commit/a8774fa5242a64783c7fcce476c99e7e82a3d158) | ||||||
|  | - New folder structure [`4dbca60`](https://git.odit.services/lfk/frontend/commit/4dbca6096fa6e0aa6ff3f1a310280cf4e56163b1) | ||||||
|  | - Fixed missing middlename action [`d6c96b7`](https://git.odit.services/lfk/frontend/commit/d6c96b781f33600fd61057e3ddb0cb0b6313a6ee) | ||||||
|  | - Formatting [`a79a87d`](https://git.odit.services/lfk/frontend/commit/a79a87de4c04f685d1209a38cab0a590274c6ded) | ||||||
|  | - Fixed donation badges now show their amount [`fb5a64c`](https://git.odit.services/lfk/frontend/commit/fb5a64c25188cd577196a80613c7aa4851d74bd5) | ||||||
|  | - Added donation route [`ccacdf2`](https://git.odit.services/lfk/frontend/commit/ccacdf274bcd55c3333e56e83081a4957b8f27db) | ||||||
|  | - Formatting [`cd9a546`](https://git.odit.services/lfk/frontend/commit/cd9a5469fd085b1204e9a513bc7e22c6f54532b8) | ||||||
|  | - Enabled add modal [`8042bca`](https://git.odit.services/lfk/frontend/commit/8042bca7ccbfe0782bf875c8dd730d0c8653ff66) | ||||||
|  | - Added custom i18n ally insert format [`1ef1053`](https://git.odit.services/lfk/frontend/commit/1ef1053d3f96a3b2cd584d27781575e329925613) | ||||||
|  | - Now routing stuff to the donation detail [`fd406eb`](https://git.odit.services/lfk/frontend/commit/fd406eb3e6ac6ee41884f7f902895756367bd3b9) | ||||||
|  | - Adjusted togle label font size [`a880ed2`](https://git.odit.services/lfk/frontend/commit/a880ed2b18a1a6ff71f9a95dc1d06242fa4bd613) | ||||||
|  | - DonorDetail accessibility improvements 👀 [`019a029`](https://git.odit.services/lfk/frontend/commit/019a0297a90695fcf1887c4bbf0310d2a81114bd) | ||||||
|  | - Updated donor badege styleing [`247ba40`](https://git.odit.services/lfk/frontend/commit/247ba4030928bcf49e15ee2e7383026c019751e9) | ||||||
|  | - Updated donor badege styleing [`fcf01ba`](https://git.odit.services/lfk/frontend/commit/fcf01ba6772340a82d7f2a703a4e66111e9f13e8) | ||||||
|  | - Removed useless style [`07636f5`](https://git.odit.services/lfk/frontend/commit/07636f51c426121d56ef27309b5f069b39843393) | ||||||
|  | - Renamed button [`1124f25`](https://git.odit.services/lfk/frontend/commit/1124f25ea3b1a2c052e2bd9c4c3480d579a8fc74) | ||||||
|  | - Amount now also self-resetts [`d2430ba`](https://git.odit.services/lfk/frontend/commit/d2430badbe8e07dc0ae0998c9595784be5d1d79e) | ||||||
|  | - Renamed folder [`7d08ea8`](https://git.odit.services/lfk/frontend/commit/7d08ea84660e735031a0511a2862429bb5222b3a) | ||||||
|  | - Merge pull request 'Donation management feature/79-donation_management' (#87) from feature/79-donation_management into dev [`03be2d0`](https://git.odit.services/lfk/frontend/commit/03be2d04923970ecb5b05f5de645a446afe3a245) | ||||||
|  | - Merge pull request 'i18n fix run no.1 feature/69-i18n_fixes' (#85) from feature/69-i18n_fixes into dev [`8b7f5a7`](https://git.odit.services/lfk/frontend/commit/8b7f5a765bcd91207957754747221f05ffdaff4b) | ||||||
|  | - Merge pull request 'Fixed refresh page reload bug' (#86) from feature/82-auth_refresh_bug into dev [`9cd9400`](https://git.odit.services/lfk/frontend/commit/9cd94004fce1847d8adb028bef36b6fb49ff144d) | ||||||
|  | - Sorted translations [`8b70882`](https://git.odit.services/lfk/frontend/commit/8b70882fecd2a0830176074dec504e417923fa50) | ||||||
|  | - Implmented donor deletion confirmation [`264868b`](https://git.odit.services/lfk/frontend/commit/264868bb6afe2066417f9a98091230cadb54ee63) | ||||||
|  | - Error message on pdf generation fail ❌ [`12bcbd2`](https://git.odit.services/lfk/frontend/commit/12bcbd28f3e5672dfb1d34d6eacfd2cc144dd4d1) | ||||||
|  | - i18n translation spree 🌍 [`7fb7ba0`](https://git.odit.services/lfk/frontend/commit/7fb7ba0d2b78065d3d6f1c407b91a0bb9fb832cd) | ||||||
|  | - Added missing translations 🌍 [`a99c022`](https://git.odit.services/lfk/frontend/commit/a99c0226084ceaea1da7a110edf2ab546b797c71) | ||||||
|  | - Implemented donor creation modal [`1b6f866`](https://git.odit.services/lfk/frontend/commit/1b6f86669c01f556e720ab5ba42b57e67a5a963d) | ||||||
|  | - Added donor detail [`cb704c4`](https://git.odit.services/lfk/frontend/commit/cb704c4551bdb46685859ec7901041900acf4c52) | ||||||
|  | - Implemented donor overview and deletion [`02087a5`](https://git.odit.services/lfk/frontend/commit/02087a541e938bfb5286290e12c5557b6b173460) | ||||||
|  | - Some i18n 🌍 [`ca8f978`](https://git.odit.services/lfk/frontend/commit/ca8f9786675952af47f663ac4593f33d35924a1a) | ||||||
|  | - Updated sponsoring logo [`3a57e1c`](https://git.odit.services/lfk/frontend/commit/3a57e1c76624c8ee41757771714970d91db0dddc) | ||||||
|  | - ✨ PDF download from TeamDetail + TeamsOverview [`dbc0ab7`](https://git.odit.services/lfk/frontend/commit/dbc0ab76af0934d3603509e218465378b99cdf63) | ||||||
|  | - basic progress toasts [`a7642c2`](https://git.odit.services/lfk/frontend/commit/a7642c2da410caf662e788951149169af2668afd) | ||||||
|  | - i18n run [`1939300`](https://git.odit.services/lfk/frontend/commit/19393006efd841220295ae588a5dbb67408261ee) | ||||||
|  | - ✨ PDF generation from OrgDetail [`0a55d73`](https://git.odit.services/lfk/frontend/commit/0a55d7314636c1041b63c5a065a69d115890b4b9) | ||||||
|  | - Now the toast hides the generation toast [`ed13a0d`](https://git.odit.services/lfk/frontend/commit/ed13a0d14b42de646e2b9e2c1566ac9c256cc1d3) | ||||||
|  | - Now using translations in org/add/address [`b7d38dd`](https://git.odit.services/lfk/frontend/commit/b7d38dd8493833322f6f1e6634ea2f08a5666fd2) | ||||||
|  | - Now the toast hides the generation toast [`8fa0be7`](https://git.odit.services/lfk/frontend/commit/8fa0be7633f41974e7e2c5e0927449717b88007a) | ||||||
|  | - Added donors to sidebar [`0cc91ac`](https://git.odit.services/lfk/frontend/commit/0cc91ac0373ec1ff1229408a6a0beb43df07244e) | ||||||
|  | - More missing translations 🌍 [`d0a48ab`](https://git.odit.services/lfk/frontend/commit/d0a48ab94b5c281692a7bd935a4eaf11d3b32eb5) | ||||||
|  | - Added missing translations [`2b037d4`](https://git.odit.services/lfk/frontend/commit/2b037d41ac0b972190c9b2c853b42c41f4148c60) | ||||||
|  | - ✨ progress toast in RunnersOverview [`4ece21c`](https://git.odit.services/lfk/frontend/commit/4ece21cdf240f203b710410b3ce9fb716ad12fef) | ||||||
|  | - Fixed missing icon [`bca9605`](https://git.odit.services/lfk/frontend/commit/bca9605d4a0ba56468d91cc4165fe15171e90c9b) | ||||||
|  | - Updated icons [`0321f0e`](https://git.odit.services/lfk/frontend/commit/0321f0e979af8336ab26563f662357e62812a4c7) | ||||||
|  | - ✒ change "Edit" table actions to "Detail" [`289a8c1`](https://git.odit.services/lfk/frontend/commit/289a8c14d335a3cb3c4333725c14c497dc6944a9) | ||||||
|  | - Fixed orgs/teams not being marked as selected on initial modal opening [`44ed633`](https://git.odit.services/lfk/frontend/commit/44ed633cbfee880b0bf7056388d04ede5f50fa47) | ||||||
|  | - Mitigated null error [`396bd22`](https://git.odit.services/lfk/frontend/commit/396bd221996bd21acafddbf1ab87fbf9b378e306) | ||||||
|  | - Fixed known translation mishaps [`02d2413`](https://git.odit.services/lfk/frontend/commit/02d24139e9974f4a8a8a62fd906efac9cabb2459) | ||||||
|  | - Added total dontaion amount to donor detail [`5d945f5`](https://git.odit.services/lfk/frontend/commit/5d945f5bc5bc5c4426da71a18c88a82ce4083f30) | ||||||
|  | - Fixed refresh page reload bug [`c569724`](https://git.odit.services/lfk/frontend/commit/c5697242ee7cef2cb9d2949a10d4efc533684401) | ||||||
|  | - Merge pull request 'Donor management feature/78-donor_mgnt' (#80) from feature/78-donor_mgnt into dev [`ad638e8`](https://git.odit.services/lfk/frontend/commit/ad638e8bc8b3f2179526d6fbd6cdf18ee2524054) | ||||||
|  | - 🖼 new donor empty image [`aec8bf5`](https://git.odit.services/lfk/frontend/commit/aec8bf56a2c03364a353e64a640bae3e8d4540a0) | ||||||
|  | - Formatting [`3e9383e`](https://git.odit.services/lfk/frontend/commit/3e9383e6d9afab2ea06cd8feaff6d52f53e161b9) | ||||||
|  | - Normalized svg [`18335e3`](https://git.odit.services/lfk/frontend/commit/18335e3325ab41caa54f1ff53bfd09bd0f590c2e) | ||||||
|  | - Fixed deletion in detail bug [`f97c2a3`](https://git.odit.services/lfk/frontend/commit/f97c2a36f69d431293ed21d20ff2e8e0782bd53d) | ||||||
|  | - Implemented currency formatting [`1c49755`](https://git.odit.services/lfk/frontend/commit/1c4975589f8ac1c5cd13b7935fb5663adde52615) | ||||||
|  | - Updated donot empty logo [`fffe5c2`](https://git.odit.services/lfk/frontend/commit/fffe5c2c4b384e3626b140a6c807b505b3dd4ae2) | ||||||
|  | - Converted total donation amount to € [`04a09c3`](https://git.odit.services/lfk/frontend/commit/04a09c3ce5e990a50e77ba235dce830983e0ff2d) | ||||||
|  | - Fixed typo [`f63e177`](https://git.odit.services/lfk/frontend/commit/f63e17775c261c994f289f9d0797be3a2c3a6a3e) | ||||||
|  | - Implemented receipt needed [`78514c6`](https://git.odit.services/lfk/frontend/commit/78514c6572793422bfaed0d9b9ee4516a30f7fe7) | ||||||
|  | - Merge pull request 'Mitigated null error' (#77) from feature/64-dialog_clearing_bug into dev [`32024cf`](https://git.odit.services/lfk/frontend/commit/32024cf2c5ade31ec0edd8150ab10e801a70dc44) | ||||||
|  | - 🐞 fixed translation keys [`d67dfdf`](https://git.odit.services/lfk/frontend/commit/d67dfdf2e7ca8f88113377690f794f440d07dd14) | ||||||
|  | - Removed key duplicate from last merge [`5b3e66c`](https://git.odit.services/lfk/frontend/commit/5b3e66c4f6f30d5ea92ac7281cd2c3a17081c9fa) | ||||||
|  | - ✨ translation keys [`a588bc4`](https://git.odit.services/lfk/frontend/commit/a588bc46319a387690a284a2dccca718f194b80b) | ||||||
|  | - ✨ basic select boxes in table [`e8f7c1c`](https://git.odit.services/lfk/frontend/commit/e8f7c1c832037af251d2e3c41600b7c7109fdaa2) | ||||||
|  | - First part of org detail address edit [`e5c31c9`](https://git.odit.services/lfk/frontend/commit/e5c31c9dd43c36d81205e100ded030309296fb5d) | ||||||
|  | - Added address to org creation dialog (styleing only) [`6d2431b`](https://git.odit.services/lfk/frontend/commit/6d2431b683b36a2595a259e69339b8b9fd0c9bdc) | ||||||
|  | - 🐞 fixed bug in OrgDetail address reactivity [`616990b`](https://git.odit.services/lfk/frontend/commit/616990b930cafe4eac57cf771b8b5d722b281218) | ||||||
|  | - Removed unused locales [`722feac`](https://git.odit.services/lfk/frontend/commit/722feac8bd0a4be5214268c0bdb321243a4d602d) | ||||||
|  | - ✨ OrgOverview - multiple pdf download [`40dda11`](https://git.odit.services/lfk/frontend/commit/40dda1150ca8ddc17b1bb64649becab3440bdfad) | ||||||
|  | - Removed unused locales [`4be87a6`](https://git.odit.services/lfk/frontend/commit/4be87a64b9df5a722b3a03893eff64f140f2dc28) | ||||||
|  | - Translated all missing translations 🌍 [`2e3750c`](https://git.odit.services/lfk/frontend/commit/2e3750c87c882c562822cee1615e74b6e84857d9) | ||||||
|  | - ✨ PDF from RunnerDetail [`3b18be5`](https://git.odit.services/lfk/frontend/commit/3b18be58747f204908e0d1065abe66e78d156da6) | ||||||
|  | - 🇩🇪 more german translations [`377d691`](https://git.odit.services/lfk/frontend/commit/377d691053966410da6dfdeb1bf2cb10009a0c0a) | ||||||
|  | - 🐞 fixed bug in Tracks datatable translation keys [`30867b4`](https://git.odit.services/lfk/frontend/commit/30867b4ba1328a405aa3ea20c1043d066f774ee0) | ||||||
|  | - 📃 pdf generation in RunnersOverview [`fa3dc87`](https://git.odit.services/lfk/frontend/commit/fa3dc870d3e114ddca472197d30e1f4178030568) | ||||||
|  | - some more translation keys [`9b0252f`](https://git.odit.services/lfk/frontend/commit/9b0252fb754cceb2c2dd374c0e4f465d6ea67f92) | ||||||
|  | - drop filepond keys [`e90fe73`](https://git.odit.services/lfk/frontend/commit/e90fe73aa25cdc077bed4446a088583126b5bd20) | ||||||
|  | - Implemented detail address add fix [`ec8d946`](https://git.odit.services/lfk/frontend/commit/ec8d946a41c8196bce8556179fa93f0ec47a7507) | ||||||
|  | - reactive button for checkboxes in table [`5e6ada1`](https://git.odit.services/lfk/frontend/commit/5e6ada140ce2722bc67f4c7c10fe95138d7d27f0) | ||||||
|  | - Genered soem runner related keys [`3c541ad`](https://git.odit.services/lfk/frontend/commit/3c541ada89aa9acee805ab0d31f7a68078bc2a69) | ||||||
|  | - Added address to org overview [`bcc7d77`](https://git.odit.services/lfk/frontend/commit/bcc7d7770ebcee95cbc6cfa69841ac15d12bc1a6) | ||||||
|  | - Runner Contact information column npow features address [`57e17f2`](https://git.odit.services/lfk/frontend/commit/57e17f2864765c13ab6cce84ede92a3be8e524fc) | ||||||
|  | - 🌎 i18n [`ff15308`](https://git.odit.services/lfk/frontend/commit/ff15308c037e6800c1bbd43d01617554e98bf2d1) | ||||||
|  | - Fixed privacy/imprint fallback bug [`b195c70`](https://git.odit.services/lfk/frontend/commit/b195c707b05ffa415b50afdf4a532e03340e1ebf) | ||||||
|  | - Formatting [`25ac84e`](https://git.odit.services/lfk/frontend/commit/25ac84e5fddd0927dc4283836a36e0f15615609f) | ||||||
|  | - Fixed clear on import bug [`e53467d`](https://git.odit.services/lfk/frontend/commit/e53467da22dee965f753500c4807fd54b02ec4be) | ||||||
|  | - Fixed clear on import bug [`09d27c0`](https://git.odit.services/lfk/frontend/commit/09d27c0b05634e7e8eefd79fa048c2cb53082abf) | ||||||
|  | - Merge commit 'b337873ca214682487844973104772539956c09a' into feature/48-usergroup-management [`266a11f`](https://git.odit.services/lfk/frontend/commit/266a11f64f073b917ebc7a0c1496d0a48a657e8a) | ||||||
|  | - Merge commit '6d0bca6d6783d3f7bbff5d413b158c6b60720bd8' into feature/48-usergroup-management [`e442b92`](https://git.odit.services/lfk/frontend/commit/e442b92a5f31d2222184aaea55de17d96ccfecbf) | ||||||
|  | - new license file version [CI SKIP] [`afd73d5`](https://git.odit.services/lfk/frontend/commit/afd73d53bee7eb2fd3244caaeb1a9867bff68721) | ||||||
|  | - Merge pull request 'Addresses for orgs and a bunch of bugfixes feature/72-adddress_for_everyone' (#73) from feature/72-adddress_for_everyone into dev [`652e55e`](https://git.odit.services/lfk/frontend/commit/652e55e80e9e7a6c14e7dbeb0ccf6259f11f9368) | ||||||
|  | - Implemented org address creation modal logic [`86f1300`](https://git.odit.services/lfk/frontend/commit/86f13003b5400ba5ea14bcc6bb9b7e7a9301c212) | ||||||
|  | - ✨ ForgotPassword demo for translation with interpolation [`505ca6a`](https://git.odit.services/lfk/frontend/commit/505ca6a58effae334f35ae99de798d85bd8fa1a2) | ||||||
|  | - 🐞 fixed address removal bug ContactDetail [`1eea935`](https://git.odit.services/lfk/frontend/commit/1eea93520749ac4fb4054004fc7ae01a02a05f68) | ||||||
|  | - Fixed wrong relation getting targeted [`4f3f7d1`](https://git.odit.services/lfk/frontend/commit/4f3f7d1edb3c4f06dcda75461f87fe3688cf20fb) | ||||||
|  | - Replaced untranslated key with already existant key [`56b5008`](https://git.odit.services/lfk/frontend/commit/56b50082782b3d62f7fe5854d0d37da3a8dd7759) | ||||||
|  | - Replaced untranslated key with already existant key [`555778f`](https://git.odit.services/lfk/frontend/commit/555778fca43fd4546968205fc41617a10d4c7727) | ||||||
|  | - Unified key translation style [`ec1a622`](https://git.odit.services/lfk/frontend/commit/ec1a6226a9f3d512404a5af7119b0508b21e03d6) | ||||||
|  | - Merge commit '9faa93e29239182871b82bca211531fb95d37b7f' into feature/69-translation-keys [`5f1c8f3`](https://git.odit.services/lfk/frontend/commit/5f1c8f3627b5603063821c1a32774d4a000606ac) | ||||||
|  | - new license file version [CI SKIP] [`3834079`](https://git.odit.services/lfk/frontend/commit/3834079481d36a38fbb6d61a56c56320c075d59d) | ||||||
|  | - Merge pull request 'component/ structure cleanup feature/68-component-cleanup' (#70) from feature/68-component-cleanup into dev [`9faa93e`](https://git.odit.services/lfk/frontend/commit/9faa93e29239182871b82bca211531fb95d37b7f) | ||||||
|  | - ✨ basic Contact components [`054c7fa`](https://git.odit.services/lfk/frontend/commit/054c7faaacfca30ab15f6fcb4241949aef4c87eb) | ||||||
|  | - Sorted locales [`e64b318`](https://git.odit.services/lfk/frontend/commit/e64b318a42741812900feaa7a825b384687bf7d2) | ||||||
|  | - ✨ basic UserGroup components [`0361f8a`](https://git.odit.services/lfk/frontend/commit/0361f8ad6991e04bc621c3201bd14d6ace9adc76) | ||||||
|  | - 🌎 Contacts i18n [`c1251d3`](https://git.odit.services/lfk/frontend/commit/c1251d333298326e25ef7573e14ed24124f621de) | ||||||
|  | - Now w/ 100% german translation 🌍 [`6c2a5f9`](https://git.odit.services/lfk/frontend/commit/6c2a5f904d6d0ab709a503130efc9e20784b9fc3) | ||||||
|  | - Added license to package [`dc0c738`](https://git.odit.services/lfk/frontend/commit/dc0c7384710985524e8caceebde06918b878ec6c) | ||||||
|  | - ✨ UserGroupsEmptyState, UserGroupsOverview, basic GroupDetail [`eddfeb1`](https://git.odit.services/lfk/frontend/commit/eddfeb10a55cbf276f29e406ad262d46ac3d1786) | ||||||
|  | - ✨ ContactDetail route [`6f4f4cc`](https://git.odit.services/lfk/frontend/commit/6f4f4ccb16d91c9ab11f65bc9c01faafa6004f5c) | ||||||
|  | - AddContactModal - allow optional address [`7138ca1`](https://git.odit.services/lfk/frontend/commit/7138ca1f5f03a6f5dc54e9d91bc1d432e354b77c) | ||||||
|  | - Initial component sort/cleanup [`c0534a3`](https://git.odit.services/lfk/frontend/commit/c0534a3b06a6f818c94adb70bca2a898ec70c2ab) | ||||||
|  | - ContactDetail - added checkbox for optional address [`894160f`](https://git.odit.services/lfk/frontend/commit/894160f3f771fa8c3566566626cfe60858fc3ab1) | ||||||
|  | - 🚧 WIP on ContactDetail [`4541304`](https://git.odit.services/lfk/frontend/commit/4541304fa8033cfc875a08faca85bab86691a1c5) | ||||||
|  | - renamed folder and removed useless files [`e1427f3`](https://git.odit.services/lfk/frontend/commit/e1427f3ecbf1e6d6dbb0b3e8c7c9514cb68c2c08) | ||||||
|  | - 🐞 fixed null addresses in ContactsOverview [`6a91bd5`](https://git.odit.services/lfk/frontend/commit/6a91bd53e2f26d72a407a266b660cbfae0902a20) | ||||||
|  | - ✨ OrgDetail - edit contact [`1586c2f`](https://git.odit.services/lfk/frontend/commit/1586c2f9e625a5eaa116251e5ebd83fcebd3bee5) | ||||||
|  | - ✨ ContactsEmptyState [`a7098df`](https://git.odit.services/lfk/frontend/commit/a7098df9cfe6ba4dffe2ed121b1e9ae69f40e89d) | ||||||
|  | - ✨ TeamDetail - edit contact [`2033572`](https://git.odit.services/lfk/frontend/commit/2033572c83654bc51ece17ef60e29c425001fe1c) | ||||||
|  | - Fixed org deletion dialog [`a4c955c`](https://git.odit.services/lfk/frontend/commit/a4c955ce8530238bcd1eb91db2cee7dae6a9fe8c) | ||||||
|  | - 🧹 ContactOverview refinement [`0f01330`](https://git.odit.services/lfk/frontend/commit/0f013304ef34d848652351a69e5374f61166db10) | ||||||
|  | - 🔗 link to ContactDetail from OrgOverview [`1a4cf21`](https://git.odit.services/lfk/frontend/commit/1a4cf211eb5d5278e8d7cf375ba534b1c24a1315) | ||||||
|  | - German spell check [`83495b1`](https://git.odit.services/lfk/frontend/commit/83495b101c851590473c769121d7a10299bcbcce) | ||||||
|  | - 🎉 working AddContactModal [`45e7f6a`](https://git.odit.services/lfk/frontend/commit/45e7f6a0d1315db8779111545f317ba746680a28) | ||||||
|  | - Removed usless console logs [`7278648`](https://git.odit.services/lfk/frontend/commit/72786486421e2161144c04c354e46eb07e00c502) | ||||||
|  | - 🎉 ContactDetail + ContactOverview [`4ef1b7a`](https://git.odit.services/lfk/frontend/commit/4ef1b7abe8458903ba91ea8aad7566205c8776c4) | ||||||
|  | - Gendered some stuff [`ce678c1`](https://git.odit.services/lfk/frontend/commit/ce678c1b769db7ab59a003752329e7598f7d86d5) | ||||||
|  | - Fixed contact update detection bug [`696d3ff`](https://git.odit.services/lfk/frontend/commit/696d3ffabf8a254480344aa5bfb1b5a4360da528) | ||||||
|  | - 🔗 link to ContactDetail from TeamsOverview [`b01fe05`](https://git.odit.services/lfk/frontend/commit/b01fe050d2a5db5f736e25859c44f8106d1eb526) | ||||||
|  | - Fixed store destrucuured import [`f086027`](https://git.odit.services/lfk/frontend/commit/f08602791040da3820b8d2317339dd0d3baa260c) | ||||||
|  | - Fixed group posting issue [`e4ae1dd`](https://git.odit.services/lfk/frontend/commit/e4ae1dd475e5c3a826eecd6af934e9c30b9a722a) | ||||||
|  | - Fixed modal multiselect [`46cd262`](https://git.odit.services/lfk/frontend/commit/46cd262fabc5d89a80283d9c8510c2a8d476a6c5) | ||||||
|  | - new license file version [CI SKIP] [`eb46c5e`](https://git.odit.services/lfk/frontend/commit/eb46c5eea65e24e1470a79b3ba9d380b4ce31a6f) | ||||||
|  | - Merge pull request 'i18n fixed + dependency bumps bugfix/99-i18n_run' (#102) from bugfix/99-i18n_run into dev [`100094e`](https://git.odit.services/lfk/frontend/commit/100094e803fdbb2bc79e2b58d9eb9a9f1b4346ae) | ||||||
|  | - Added select workaround for all things team🏠 [`5ad42d6`](https://git.odit.services/lfk/frontend/commit/5ad42d6ca7a3823a265e54d9dc7835e4a3e2e89c) | ||||||
|  | - Implemented svelt select bug workaround for scan detail🔥🔥🔥 [`cda4512`](https://git.odit.services/lfk/frontend/commit/cda45128223ccc47456e2548b048e371d80bb7c2) | ||||||
|  | - Fix for bug discovered by @philipp [`d28a0e1`](https://git.odit.services/lfk/frontend/commit/d28a0e1dbb877b3e369b106a103a3bfc52dd1e6a) | ||||||
|  | - Added german translations for the new keys [`635e2ba`](https://git.odit.services/lfk/frontend/commit/635e2ba0e07c04ef0e3b35438193521e2ed2368b) | ||||||
|  | - Fix for bug discovered by @philipp [`94d52df`](https://git.odit.services/lfk/frontend/commit/94d52df322fca1790776300b2d4be6a3996cd57f) | ||||||
|  | - Svelte select is now 100% keyboard useable (or at least in one modal it is....) [`eb6af4b`](https://git.odit.services/lfk/frontend/commit/eb6af4b4f0b561653fe57ec41c272cb1cb127ecc) | ||||||
|  | - Added patime to track scan detail [`937265e`](https://git.odit.services/lfk/frontend/commit/937265e82890c26046ccf321a3a4d6b9258674a9) | ||||||
|  | - sorted translations 🌍 [`e723cbf`](https://git.odit.services/lfk/frontend/commit/e723cbf3b301ecb0de6426345def3fe53618a29c) | ||||||
|  | - Removed lodash as a dependency 🗑 [`f09224d`](https://git.odit.services/lfk/frontend/commit/f09224d5c02a2c2d2f1ac221314d2c22758143ba) | ||||||
|  | - Added translation keys [`99c3050`](https://git.odit.services/lfk/frontend/commit/99c30504115b068b65a48c651091252371b180ae) | ||||||
|  | - Found a hiddeen missing key👀👀 [`00d16ef`](https://git.odit.services/lfk/frontend/commit/00d16ef59f2af3a540708cd61c195ea0ead86995) | ||||||
|  | - Bumped svelte-* dependencies (non-dev)🔝 [`b4e7f90`](https://git.odit.services/lfk/frontend/commit/b4e7f9046c29aa45661f175f7b8cdca5d6a1a9a0) | ||||||
|  | - Bumped svelte-related dev dependencies🔥 [`5204ba5`](https://git.odit.services/lfk/frontend/commit/5204ba5e24ca6e8ab0c5ad0255c54eede15a3baf) | ||||||
|  | - Bumped router [`1b9b9ed`](https://git.odit.services/lfk/frontend/commit/1b9b9ed3723b0c12ab3b65eb0d94ec8c2c16ef42) | ||||||
|  | - new license file version [CI SKIP] [`7521ad8`](https://git.odit.services/lfk/frontend/commit/7521ad8bbbb094bbe9ba723d03b3e9eb7f6a3243) | ||||||
|  | - Merge pull request 'Scan management feature/92-scan_mgnt' (#101) from feature/92-scan_mgnt into dev [`b994065`](https://git.odit.services/lfk/frontend/commit/b994065e1810c44f4f9373b71baf632cccb34967) | ||||||
|  | - Merge pull request 'Svelte select dropdown fix bugfix/98-dropdowns' (#100) from bugfix/98-dropdowns into dev [`a7fb2b8`](https://git.odit.services/lfk/frontend/commit/a7fb2b8a1a1e36a420ecbfce0ae41f685d6e24f8) | ||||||
|  | - Sorted translations [`95eb8b6`](https://git.odit.services/lfk/frontend/commit/95eb8b6ae4d427c3ed7c64ac2d43b27545a64e3d) | ||||||
|  | - Basic scan detail [`107360c`](https://git.odit.services/lfk/frontend/commit/107360cd93f00beb73bf0e3a50ca45fbfed63dfc) | ||||||
|  | - Fixed emptystate 🛠 [`e9d5527`](https://git.odit.services/lfk/frontend/commit/e9d5527482b2dd2c61b83642051c4f808906139b) | ||||||
|  | - Implemented basic scan creation [`1ada5d9`](https://git.odit.services/lfk/frontend/commit/1ada5d9c2c4d78868ca87b5cb05c0b6d770c7e9c) | ||||||
|  | - Bugfix for download button dropdown outsideclick [`6a925cb`](https://git.odit.services/lfk/frontend/commit/6a925cb27f06101a7292aae7e77223a0cd204dfa) | ||||||
|  | - Added basic scan overview [`eb0910b`](https://git.odit.services/lfk/frontend/commit/eb0910be575f4d83ab4f81a75a76cdfac46db19b) | ||||||
|  | - Advanced Scan detail [`284bdc6`](https://git.odit.services/lfk/frontend/commit/284bdc6e33508fe4e277ea5fd312bea84881c882) | ||||||
|  | - Added contact selection via svelte select [`7edc342`](https://git.odit.services/lfk/frontend/commit/7edc3427e10166d2a5e1b911cc459b26b37f984b) | ||||||
|  | - Runner detail now uses svelte select🔥🔥 [`cee1ab1`](https://git.odit.services/lfk/frontend/commit/cee1ab1347beb80f7e391f41117b25ecef212032) | ||||||
|  | - Added formatted laptime [`5afa541`](https://git.odit.services/lfk/frontend/commit/5afa541b303d9a650b5fd5a46cd4dd7b839d6085) | ||||||
|  | - Added scans to sidebar (including a new icon) [`2cf8e02`](https://git.odit.services/lfk/frontend/commit/2cf8e0291ada3a9f70a8172ec95ed98be86ad7d0) | ||||||
|  | - Adjusted filter [`53aa3bc`](https://git.odit.services/lfk/frontend/commit/53aa3bc3ae111399b22a122ad8912d78a12b8d79) | ||||||
|  | - Org detail now uses svelte select [`6b59067`](https://git.odit.services/lfk/frontend/commit/6b590671bcb95c8c223860137ffbf2c82d05a144) | ||||||
|  | - Add runner now uses svelte-select [`0e682bf`](https://git.odit.services/lfk/frontend/commit/0e682bf6302aea388ecfe6c92ad571db04d1c496) | ||||||
|  | - Added basic files for scans [`f67e089`](https://git.odit.services/lfk/frontend/commit/f67e089ff39aa885983c063b82b60ba0ea432b69) | ||||||
|  | - Added new scan icon to add scan modal [`9e5a093`](https://git.odit.services/lfk/frontend/commit/9e5a093a3a472ad06c7f0f0faee3b3ad318c8c44) | ||||||
|  | - Now using svelte-select [`2a644d7`](https://git.odit.services/lfk/frontend/commit/2a644d70704cd3ffc5f0d7dd7894b2389ab90b1f) | ||||||
|  | - MAde detail editable through the more reacctive process [`8b95b30`](https://git.odit.services/lfk/frontend/commit/8b95b300e283b25a9e25acbd6ffd5c003e30e59c) | ||||||
|  | - Add Team now uses the new select [`1da1578`](https://git.odit.services/lfk/frontend/commit/1da15783d56ff4a998f872ce6842e83707b274ff) | ||||||
|  | - Formatting [`e1bd364`](https://git.odit.services/lfk/frontend/commit/e1bd364278e590b8081c10fae3f642b91936c69b) | ||||||
|  | - Fixed broken change detection [`a45c5da`](https://git.odit.services/lfk/frontend/commit/a45c5da0a7b9ef8f3a60b2b50cb8d1bd68d6ede8) | ||||||
|  | - Fixed bugs with stuff not being displayed🛠 [`ff1bc8a`](https://git.odit.services/lfk/frontend/commit/ff1bc8a44a0d020d57aaba5a3eb255a22d65bb3d) | ||||||
|  | - Now routing scans "start" page [`915bbbb`](https://git.odit.services/lfk/frontend/commit/915bbbbde023c34feb4595629d59546970ba8c94) | ||||||
|  | - Added translations 🌎 [`8acbfa8`](https://git.odit.services/lfk/frontend/commit/8acbfa89674130667da11bca00879a30163b3523) | ||||||
|  | - Now checking selectables for not being null [`5a2172b`](https://git.odit.services/lfk/frontend/commit/5a2172bb9b878c77c4f09e65237ce1a179c95b8c) | ||||||
|  | - formatting [`9d0c6b9`](https://git.odit.services/lfk/frontend/commit/9d0c6b9ef44846bf59a521b71b2c4d9e91189ed5) | ||||||
|  | - Fixed initial select value [`d3a3de2`](https://git.odit.services/lfk/frontend/commit/d3a3de2eac40503a5c626e59f9b5036e482b7399) | ||||||
|  | - Small bugfixes [`dfa38d3`](https://git.odit.services/lfk/frontend/commit/dfa38d34215921a10619de71c7a7cf390be3b722) | ||||||
|  | - Added clear event [`ee0c149`](https://git.odit.services/lfk/frontend/commit/ee0c1496e67316a7473fdcccecad67cb1d10cdf9) | ||||||
|  | - Now routing scan detail [`abf9aa4`](https://git.odit.services/lfk/frontend/commit/abf9aa475b62ead1bfa185c39f8934fba508f9d8) | ||||||
|  | - Small bugfixes [`6e04b71`](https://git.odit.services/lfk/frontend/commit/6e04b71c1aaed142152f1ef045c69f02ee116a3f) | ||||||
|  | - Reset array [`8252a35`](https://git.odit.services/lfk/frontend/commit/8252a35771bbde82cfcf4f049be8536193df9a17) | ||||||
|  | - Removed useless console.logs [`fc668c6`](https://git.odit.services/lfk/frontend/commit/fc668c68806bc0b132d818d64ee2c833ce3ace06) | ||||||
|  | - Bumped lfk lib version [`bb7f2a6`](https://git.odit.services/lfk/frontend/commit/bb7f2a611a199f59932f61be36464ed654d76a06) | ||||||
|  | - Fixed visual bug (overflow) [`4e51b12`](https://git.odit.services/lfk/frontend/commit/4e51b128e6d13d222b196175ddc5fbc5c1f9597e) | ||||||
|  | - Bumped svelte select version [`0277263`](https://git.odit.services/lfk/frontend/commit/0277263f9825e78812e0518d0a849535c0fe99a4) | ||||||
|  | - Removed depreciated information [`99fb420`](https://git.odit.services/lfk/frontend/commit/99fb420d5804c6061d806f518d8d3e742b20eacb) | ||||||
|  | - Merge pull request 'Make dropdowns (selects) searchable feature/91-searchable_dropdowns' (#97) from feature/91-searchable_dropdowns into dev [`b541c93`](https://git.odit.services/lfk/frontend/commit/b541c93797b1a0317fee8c934e9803a9f2f7677d) | ||||||
|  | - Fixed typo✏ [`e6df764`](https://git.odit.services/lfk/frontend/commit/e6df76456204282c7724f62ada6a902c771ec451) | ||||||
|  | - Reapplied change from dev [`b841cc8`](https://git.odit.services/lfk/frontend/commit/b841cc8b959546d04daa0bacc04265a95573cd12) | ||||||
|  | - Removed console log 🤫 [`bc2a8ca`](https://git.odit.services/lfk/frontend/commit/bc2a8caf3e3f87de11a7402c433d95d1851b4e4c) | ||||||
|  | - Applied Docker MTU fix 🛠 [`f24b2b9`](https://git.odit.services/lfk/frontend/commit/f24b2b9b4cdb5e00eacea35be3199a38352d8a55) | ||||||
|  | - Small bugfix 🛠 [`1a115a8`](https://git.odit.services/lfk/frontend/commit/1a115a842350da0b0ece58a9b5176a90897db9b7) | ||||||
|  | - Merge pull request 'Scan station management feature/93-scan_stations' (#95) from feature/93-scan_stations into dev [`d00446d`](https://git.odit.services/lfk/frontend/commit/d00446dc7b27d67589a7eb7f251b6b8d60dedd43) | ||||||
|  | - Fixed case sensitivity [`c6db6c5`](https://git.odit.services/lfk/frontend/commit/c6db6c553588c7cfb33fd3105122b63ecf6c12fb) | ||||||
|  | - Sorted translations [`74c042a`](https://git.odit.services/lfk/frontend/commit/74c042a86b7e98ee0dd7f28981836ca33d1d6576) | ||||||
|  | - Sorted translations 👀 [`a5d1b76`](https://git.odit.services/lfk/frontend/commit/a5d1b7689161ac491ab6aa68aad3625e5207bdb6) | ||||||
|  | - Added translations for runner searching [`8c4a54e`](https://git.odit.services/lfk/frontend/commit/8c4a54eb07c8c6cc9ee4ea1b5c620a12bdabc0e3) | ||||||
|  | - Sorted translations [`476f919`](https://git.odit.services/lfk/frontend/commit/476f9191211c473244f2e05a1d840eabada8190a) | ||||||
|  | - Added search languagke keys [`47f0cd0`](https://git.odit.services/lfk/frontend/commit/47f0cd0b589729ea0d60e12c3f3ead7a2538e9ff) | ||||||
|  | - i18n run: Added keys 🌍 [`50aa891`](https://git.odit.services/lfk/frontend/commit/50aa8917090af743fa99e9da79327496640a0014) | ||||||
|  | - Added new translation keys 🌍 [`1aa2b3b`](https://git.odit.services/lfk/frontend/commit/1aa2b3b065816f82c85b5320261a10b9c4d1879c) | ||||||
|  | - Added german translations 🇩🇪 [`e6d80c8`](https://git.odit.services/lfk/frontend/commit/e6d80c8ccb1e15cbac421b883da07be370c89456) | ||||||
|  | - Added german translations [`e93f4e9`](https://git.odit.services/lfk/frontend/commit/e93f4e99f9f768b7e2b81088c3d47c3363197ab2) | ||||||
|  | - Basic scanstation creation [`9ee7685`](https://git.odit.services/lfk/frontend/commit/9ee768551f0d617257452f7a89992f25927de02a) | ||||||
|  | - Added scanstation detail [`258b3ce`](https://git.odit.services/lfk/frontend/commit/258b3cea66148a1a2beb14dd7aba5c785ff30c83) | ||||||
|  | - Added station token copy modal [`8856671`](https://git.odit.services/lfk/frontend/commit/88566719ec4eab243369f9f26151e9f6377ddfa6) | ||||||
|  | - Added station deletion confirmation dialog [`9f754ef`](https://git.odit.services/lfk/frontend/commit/9f754ef0e98ab78548391796db96e69b15fcd20e) | ||||||
|  | - New fancy selects for donation details [`76be8d5`](https://git.odit.services/lfk/frontend/commit/76be8d5a8766c55e59e34dab2bfa0d24734b7886) | ||||||
|  | - Spelling+Formatting [`7ff1d50`](https://git.odit.services/lfk/frontend/commit/7ff1d5007935946423d49eb559cb9e15f7dbb023) | ||||||
|  | - Finished scanstationmodal (without i18n) [`83e782c`](https://git.odit.services/lfk/frontend/commit/83e782c7c567d3747eb4e0a4313f6e7de6f2355f) | ||||||
|  | - Now with custom label generation functions [`48b8dfe`](https://git.odit.services/lfk/frontend/commit/48b8dfe973c880f4bad5884cd49769f14ba0f78d) | ||||||
|  | - Updated ci secrets and type [`65111e8`](https://git.odit.services/lfk/frontend/commit/65111e87c1dc5a397f47b94282a18c0eb9888794) | ||||||
|  | - New select [`fe16c66`](https://git.odit.services/lfk/frontend/commit/fe16c66cf2d9ee2a950b0865b43fe7797b9540b2) | ||||||
|  | - Added custom placeholders [`1c330d0`](https://git.odit.services/lfk/frontend/commit/1c330d0301d5f3167a49161974eb09ff2324ba16) | ||||||
|  | - Added new select for runners [`dab5bee`](https://git.odit.services/lfk/frontend/commit/dab5bee3c0ed48f0e7562b2d829a25cb8b80f2c6) | ||||||
|  | - Added scanstations to sidebar [`4b47e70`](https://git.odit.services/lfk/frontend/commit/4b47e70b13507034dc321a80e4e61fd387749571) | ||||||
|  | - Added fancier active states [`95b1490`](https://git.odit.services/lfk/frontend/commit/95b1490f8493168a07e3db167ee8c9ac81730429) | ||||||
|  | - Finished scanstations base view [`85fa9d9`](https://git.odit.services/lfk/frontend/commit/85fa9d942ea8e8d636509bf0db79b56f4c1a20b8) | ||||||
|  | - Added missing clear [`1bc8404`](https://git.odit.services/lfk/frontend/commit/1bc840430f6c6e886d38c50a1c5b31ae2204a615) | ||||||
|  | - Added icon 🖼 [`e8e3ddc`](https://git.odit.services/lfk/frontend/commit/e8e3ddceff08e9b3b024da8e6b534a86b35f6f39) | ||||||
|  | - Added translation strings [`88ad64f`](https://git.odit.services/lfk/frontend/commit/88ad64f113878784dfc0808ffdc83c8aca85588f) | ||||||
|  | - Added custom filter/search [`f97be4e`](https://git.odit.services/lfk/frontend/commit/f97be4e7291f45f9240a3491603b9c0982b37a68) | ||||||
|  | - Merge pull request 'Well that was less work than expected ....' (#96) from feature/90-translations into dev [`64b6c4d`](https://git.odit.services/lfk/frontend/commit/64b6c4d5f7a6f56370336828f05a56520976135d) | ||||||
|  | - And with working i18n 🌍 [`e07d1e4`](https://git.odit.services/lfk/frontend/commit/e07d1e42e2ed4e21d053aef85432c9dbb9f103f4) | ||||||
|  | - Sorted translations [`6870e31`](https://git.odit.services/lfk/frontend/commit/6870e31a8143187d98e39f1964139a0f57328779) | ||||||
|  | - Sorted translations [`e487213`](https://git.odit.services/lfk/frontend/commit/e4872131c84d5021a0b02a944550fbcd99fb5c1a) | ||||||
|  | - Added translation keyz [`3693025`](https://git.odit.services/lfk/frontend/commit/36930259d2bd250af8453ac3ed9349503c5f109d) | ||||||
|  | - Translated stuff 🌍 [`dcaca2e`](https://git.odit.services/lfk/frontend/commit/dcaca2ecbded329b3240a30e8b2d51fc392483eb) | ||||||
|  | - Moved pdf generation to function instead of onclick for all components [`22e9f53`](https://git.odit.services/lfk/frontend/commit/22e9f53c42d27177bd101d3724ff7e640850f5f6) | ||||||
|  | - New download buttons for everyone (that can generate sponsoring contracts) [`e24b84e`](https://git.odit.services/lfk/frontend/commit/e24b84e7095fc929d1c160646dfaa01a260f229b) | ||||||
|  | - Now routing to gorup permissions [`af7e44c`](https://git.odit.services/lfk/frontend/commit/af7e44cf7cb168eb9d017951c5e9cf4e0ead4673) | ||||||
|  | - Now routing scan statins overview [`ca9c390`](https://git.odit.services/lfk/frontend/commit/ca9c390bb265e5c1b1f8752e118c5f3c560ddc82) | ||||||
|  | - Implemented rough outside click handler for the dropdown [`c2bd696`](https://git.odit.services/lfk/frontend/commit/c2bd696bfedcfdaf0b13eb165d800691d58ca010) | ||||||
|  | - Working button onklicks [`3b7c25b`](https://git.odit.services/lfk/frontend/commit/3b7c25b106b5369e622fabb844302463a55971b5) | ||||||
|  | - Added basic table for scanstations [`c53b579`](https://git.odit.services/lfk/frontend/commit/c53b579fca6f65b8d34ab63a8ec8321100e563bd) | ||||||
|  | - Added permissions list to usergroup detail [`05099d0`](https://git.odit.services/lfk/frontend/commit/05099d066bb383b4883b4fae59da35e419935827) | ||||||
|  | - Working suergroup permissions overview [`7c32486`](https://git.odit.services/lfk/frontend/commit/7c324869a463d07af8c8f43b1853544c4a0d3440) | ||||||
|  | - Switched the icon style [`305b18e`](https://git.odit.services/lfk/frontend/commit/305b18ef57c885d7cd1295557f8651f546b7934a) | ||||||
|  | - Basic sponsoring language dropdown for runners [`6079e1f`](https://git.odit.services/lfk/frontend/commit/6079e1fa90249cfc94ed210fb40bd6fcf7e9ec11) | ||||||
|  | - Updated users icon [`8ebc88a`](https://git.odit.services/lfk/frontend/commit/8ebc88aebb5af1fc6361582745d57d37ea6905e1) | ||||||
|  | - You can now delete a station from it's detail [`c4acf77`](https://git.odit.services/lfk/frontend/commit/c4acf774ec6ab59eb24da437548667a5d26d624b) | ||||||
|  | - Updated users icon [`c111ec9`](https://git.odit.services/lfk/frontend/commit/c111ec9d9113adac7f19fc5f0b527e0755cafd0e) | ||||||
|  | - More i18n 🌍 [`e85cdaf`](https://git.odit.services/lfk/frontend/commit/e85cdaf3240abc78f9bf6353e911f157a06f11ca) | ||||||
|  | - Added group detail routing [`937486a`](https://git.odit.services/lfk/frontend/commit/937486a66bd0c8bacbb867c469e7a69c30be2a5e) | ||||||
|  | - Changed row order [`773fbfc`](https://git.odit.services/lfk/frontend/commit/773fbfc579014dcf67c87b43ab0a0d9d11ea23d6) | ||||||
|  | - Added missing translations 🌍 [`599d340`](https://git.odit.services/lfk/frontend/commit/599d340a72a9577a28fd652648558fee1ccd6bf1) | ||||||
|  | - Added missing translations 🌍 [`89b7fb8`](https://git.odit.services/lfk/frontend/commit/89b7fb8072bdc463cd9ac2926fef7f92c9b7130c) | ||||||
|  | - Now with dropdown aiutoclose [`c89caf7`](https://git.odit.services/lfk/frontend/commit/c89caf78558d379ad587b37375280d5319c51cd2) | ||||||
|  | - Now routing scan station detail [`a3daa2d`](https://git.odit.services/lfk/frontend/commit/a3daa2d24f5ed76fbde5d073360b2ca0a98fa9ac) | ||||||
|  | - You can now add scanstations [`e45f8fa`](https://git.odit.services/lfk/frontend/commit/e45f8fa9efabcb18f69577a368235ee568dc16cb) | ||||||
|  | - Clicking on a dropdown option now closes it everywhere [`9fec315`](https://git.odit.services/lfk/frontend/commit/9fec31591007253ced7a9c8d29552980138a5971) | ||||||
|  | - Removed locale overrides [`9a8a978`](https://git.odit.services/lfk/frontend/commit/9a8a978e4959b36c5d6beccdcec6c313e1529e65) | ||||||
|  | - Udergroup permission reactivity fix [`bfc9315`](https://git.odit.services/lfk/frontend/commit/bfc93158f50bfa78a26340b609dffcc6b164f90c) | ||||||
|  | - Added "tooltip" [`2de861d`](https://git.odit.services/lfk/frontend/commit/2de861d4c13da8327b2bd9ec34f17c71e83105fc) | ||||||
|  | - Fixed nameing [`5e417f0`](https://git.odit.services/lfk/frontend/commit/5e417f0714c3aa0189d9a7148d81d48ca922802b) | ||||||
|  | - Changed group icon [`16e1434`](https://git.odit.services/lfk/frontend/commit/16e1434f2a53270d237a9345d55748f9b3d0460b) | ||||||
|  | - New image for emptystate [`e8de1f6`](https://git.odit.services/lfk/frontend/commit/e8de1f6d9c120636f51f7e916692690050060719) | ||||||
|  | - Added german translation 🇩🇪 [`95fcd1d`](https://git.odit.services/lfk/frontend/commit/95fcd1dcc49247a620e030084674c23c871472b6) | ||||||
|  | - Fixed routing [`891ea2d`](https://git.odit.services/lfk/frontend/commit/891ea2da12679049c018e87b8474af91978d9f56) | ||||||
|  | - Well that was less work than expected .... [`8d8695b`](https://git.odit.services/lfk/frontend/commit/8d8695ba13b0183b5f1535bd48c162bb809b01f0) | ||||||
|  | - Added cursor-pointer [`27a1f57`](https://git.odit.services/lfk/frontend/commit/27a1f57ed34577f0c6865161559081763d29c6dc) | ||||||
|  | - Spelling [`bd22d3b`](https://git.odit.services/lfk/frontend/commit/bd22d3be3626429fb3d82c881dfe58c41fdcd00b) | ||||||
|  | - Switched pipeline type to kubernetes [`ba9d458`](https://git.odit.services/lfk/frontend/commit/ba9d4587cb5ff203b5ad67681c333834ac3213a9) | ||||||
|  | - Fixed emptystate svg [`870e772`](https://git.odit.services/lfk/frontend/commit/870e772da27d1ee2bf59b4d110cd3161bc52a865) | ||||||
|  | - new license file version [CI SKIP] [`7d654f4`](https://git.odit.services/lfk/frontend/commit/7d654f4a208b42e43899d770137b6dc90a2a2aa4) | ||||||
|  | - Merge pull request 'Spnonsoring contract language selector feature/84-sponsoringcontract_language_selector' (#89) from feature/84-sponsoringcontract_language_selector into dev [`b810bb0`](https://git.odit.services/lfk/frontend/commit/b810bb01dbbe3d506af3852fcda8e1365a570c70) | ||||||
|  | - Merge pull request 'Usergroup management in the UI feature/48-usergroup-management' (#88) from feature/48-usergroup-management into dev [`434466b`](https://git.odit.services/lfk/frontend/commit/434466b306ec11ad46e5ee99bec22bdcd01872b6) | ||||||
|  | - Fixed root breadcrumb linking [`e1ac35f`](https://git.odit.services/lfk/frontend/commit/e1ac35f848e810191b98b2dc2b9f6ec7e4975f6f) | ||||||
|  | - Added missing translations 🌍 [`29f99f0`](https://git.odit.services/lfk/frontend/commit/29f99f0b2047686327fe8c6f6ae68427c6db724b) | ||||||
|  | - Formatting [`b8725c9`](https://git.odit.services/lfk/frontend/commit/b8725c96cdf8057498503728d8c8a7934983d14c) | ||||||
|  | - Fixed Back linking [`4397566`](https://git.odit.services/lfk/frontend/commit/4397566f1e9ce8f0f1e5a6ba7c3c500dddb64bd0) | ||||||
|  | - Fix for user permission availdable [`7e80608`](https://git.odit.services/lfk/frontend/commit/7e80608066d158221a36cb251d2ba4dd2a27c785) | ||||||
|  | - Dependency bump 👊 [`2b57d49`](https://git.odit.services/lfk/frontend/commit/2b57d49e4e703ca15dc151ab69614dd893e8a0a5) | ||||||
|  | - Removed filepond [`ca41f4d`](https://git.odit.services/lfk/frontend/commit/ca41f4d4f26871b2768a1cc1d438cd99f21956b9) | ||||||
|  | - Reimported simple.css [`e2fb9a6`](https://git.odit.services/lfk/frontend/commit/e2fb9a66adbc2ecb12b3705a24fd6469d0f3af38) | ||||||
|  | - Fixed store not being found [`543b3bd`](https://git.odit.services/lfk/frontend/commit/543b3bd937bdb8e0ac1fabee3c996be5d7874348) | ||||||
|  | - new license file version [CI SKIP] [`0c9785a`](https://git.odit.services/lfk/frontend/commit/0c9785af36807608970ff3c91549c7b694a9d5c4) | ||||||
|  | - Fixed address update bug [`14e5d0e`](https://git.odit.services/lfk/frontend/commit/14e5d0e7405f010e0d55aa31b0f3fd5f050d4f57) | ||||||
|  | - Removed debug output [`27609dc`](https://git.odit.services/lfk/frontend/commit/27609dc5e0bf9a78ef37f0b3b74cc2ca25bfddbf) | ||||||
|  | - Merge pull request 'Translated everything feature/65-translations' (#66) from feature/65-translations into dev [`3f54180`](https://git.odit.services/lfk/frontend/commit/3f5418083ff1a3ce883563610c8e4c67244df64f) | ||||||
|  | - Fixed import modal width [`d822e4a`](https://git.odit.services/lfk/frontend/commit/d822e4ab3f61a019c9bdea3e56acdea8711f1b8f) | ||||||
|  | - User permission update reactivity fix [`5994b22`](https://git.odit.services/lfk/frontend/commit/5994b22464e38fcde6f1073da073782d00dfbdc8) | ||||||
|  |  | ||||||
| #### [0.6.0](https://git.odit.services/lfk/frontend/compare/0.5.0...0.6.0) | #### [0.6.0](https://git.odit.services/lfk/frontend/compare/0.5.0...0.6.0) | ||||||
|  |  | ||||||
|  | > 12 February 2021 | ||||||
|  |  | ||||||
| - Merge pull request 'feature/52-runner-filters' (#63) from feature/52-runner-filters into dev [`#52`](https://git.odit.services/lfk/frontend/issues/52) | - Merge pull request 'feature/52-runner-filters' (#63) from feature/52-runner-filters into dev [`#52`](https://git.odit.services/lfk/frontend/issues/52) | ||||||
| - Merge pull request 'feature/43-password-reset' (#61) from feature/43-password-reset into dev [`#43`](https://git.odit.services/lfk/frontend/issues/43) | - Merge pull request 'feature/43-password-reset' (#61) from feature/43-password-reset into dev [`#43`](https://git.odit.services/lfk/frontend/issues/43) | ||||||
| - Merge pull request 'feature/51-teamoverview-badge-org' (#59) from feature/51-teamoverview-badge-org into dev [`#51`](https://git.odit.services/lfk/frontend/issues/51) | - Merge pull request 'feature/51-teamoverview-badge-org' (#59) from feature/51-teamoverview-badge-org into dev [`#51`](https://git.odit.services/lfk/frontend/issues/51) | ||||||
| @@ -19,6 +404,7 @@ All notable changes to this project will be documented in this file. Dates are d | |||||||
| - 👀 ResetPassword - success and error states [`8b2f196`](https://git.odit.services/lfk/frontend/commit/8b2f1965e2a754da6e40a3051e8ae3e771d70336) | - 👀 ResetPassword - success and error states [`8b2f196`](https://git.odit.services/lfk/frontend/commit/8b2f1965e2a754da6e40a3051e8ae3e771d70336) | ||||||
| - added Privacy page [`5741cbe`](https://git.odit.services/lfk/frontend/commit/5741cbe7562542ba2b81a9a6d6be7fb0f5145801) | - added Privacy page [`5741cbe`](https://git.odit.services/lfk/frontend/commit/5741cbe7562542ba2b81a9a6d6be7fb0f5145801) | ||||||
| - ImportRunnerModal - differenciate between team and org import [`acf0562`](https://git.odit.services/lfk/frontend/commit/acf0562851a77b9122473ffb1753a94b4272e53b) | - ImportRunnerModal - differenciate between team and org import [`acf0562`](https://git.odit.services/lfk/frontend/commit/acf0562851a77b9122473ffb1753a94b4272e53b) | ||||||
|  | - 🚀RELEASE v0.6.0 [`087c85e`](https://git.odit.services/lfk/frontend/commit/087c85e58674e317cbe11bd135d3f051defa7911) | ||||||
| - ✨ RunnersOverview - basic working filter [`575b4ce`](https://git.odit.services/lfk/frontend/commit/575b4ce9708625fbec23c49101f44825c6a75bce) | - ✨ RunnersOverview - basic working filter [`575b4ce`](https://git.odit.services/lfk/frontend/commit/575b4ce9708625fbec23c49101f44825c6a75bce) | ||||||
| - basic select filtering in RunnersOverview [`e415258`](https://git.odit.services/lfk/frontend/commit/e415258787c776d4a5291632f47c2fcceba9a040) | - basic select filtering in RunnersOverview [`e415258`](https://git.odit.services/lfk/frontend/commit/e415258787c776d4a5291632f47c2fcceba9a040) | ||||||
| - WIP on filter [`e23821a`](https://git.odit.services/lfk/frontend/commit/e23821a7cbe73fda420e4bcaaa2dbf5a89b56cc9) | - WIP on filter [`e23821a`](https://git.odit.services/lfk/frontend/commit/e23821a7cbe73fda420e4bcaaa2dbf5a89b56cc9) | ||||||
|   | |||||||
							
								
								
									
										362
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,362 @@ | |||||||
|  | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Creative | ||||||
|  | Commons Corporation ("Creative Commons") is not a law firm and does not provide | ||||||
|  | legal services or legal advice. Distribution of Creative Commons public licenses | ||||||
|  | does not create a lawyer-client or other relationship. Creative Commons makes | ||||||
|  | its licenses and related information available on an "as-is" basis. Creative | ||||||
|  | Commons gives no warranties regarding its licenses, any material licensed | ||||||
|  | under their terms and conditions, or any related information. Creative Commons | ||||||
|  | disclaims all liability for damages resulting from their use to the fullest | ||||||
|  | extent possible. | ||||||
|  |  | ||||||
|  | Using Creative Commons Public Licenses | ||||||
|  |  | ||||||
|  | Creative Commons public licenses provide a standard set of terms and conditions | ||||||
|  | that creators and other rights holders may use to share original works of | ||||||
|  | authorship and other material subject to copyright and certain other rights | ||||||
|  | specified in the public license below. The following considerations are for | ||||||
|  | informational purposes only, are not exhaustive, and do not form part of our | ||||||
|  | licenses. | ||||||
|  |  | ||||||
|  | Considerations for licensors: Our public licenses are intended for use by | ||||||
|  | those authorized to give the public permission to use material in ways otherwise | ||||||
|  | restricted by copyright and certain other rights. Our licenses are irrevocable. | ||||||
|  | Licensors should read and understand the terms and conditions of the license | ||||||
|  | they choose before applying it. Licensors should also secure all rights necessary | ||||||
|  | before applying our licenses so that the public can reuse the material as | ||||||
|  | expected. Licensors should clearly mark any material not subject to the license. | ||||||
|  | This includes other CC-licensed material, or material used under an exception | ||||||
|  | or limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors | ||||||
|  |  | ||||||
|  | Considerations for the public: By using one of our public licenses, a licensor | ||||||
|  | grants the public permission to use the licensed material under specified | ||||||
|  | terms and conditions. If the licensor's permission is not necessary for any | ||||||
|  | reason–for example, because of any applicable exception or limitation to copyright–then | ||||||
|  | that use is not regulated by the license. Our licenses grant only permissions | ||||||
|  | under copyright and certain other rights that a licensor has authority to | ||||||
|  | grant. Use of the licensed material may still be restricted for other reasons, | ||||||
|  | including because others have copyright or other rights in the material. A | ||||||
|  | licensor may make special requests, such as asking that all changes be marked | ||||||
|  | or described. Although not required by our licenses, you are encouraged to | ||||||
|  | respect those requests where reasonable. More considerations for the public | ||||||
|  | : wiki.creativecommons.org/Considerations_for_licensees | ||||||
|  |  | ||||||
|  | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public | ||||||
|  | License | ||||||
|  |  | ||||||
|  | By exercising the Licensed Rights (defined below), You accept and agree to | ||||||
|  | be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike | ||||||
|  | 4.0 International Public License ("Public License"). To the extent this Public | ||||||
|  | License may be interpreted as a contract, You are granted the Licensed Rights | ||||||
|  | in consideration of Your acceptance of these terms and conditions, and the | ||||||
|  | Licensor grants You such rights in consideration of benefits the Licensor | ||||||
|  | receives from making the Licensed Material available under these terms and | ||||||
|  | conditions. | ||||||
|  |  | ||||||
|  | Section 1 – Definitions. | ||||||
|  |  | ||||||
|  | a. Adapted Material means material subject to Copyright and Similar Rights | ||||||
|  | that is derived from or based upon the Licensed Material and in which the | ||||||
|  | Licensed Material is translated, altered, arranged, transformed, or otherwise | ||||||
|  | modified in a manner requiring permission under the Copyright and Similar | ||||||
|  | Rights held by the Licensor. For purposes of this Public License, where the | ||||||
|  | Licensed Material is a musical work, performance, or sound recording, Adapted | ||||||
|  | Material is always produced where the Licensed Material is synched in timed | ||||||
|  | relation with a moving image. | ||||||
|  |  | ||||||
|  | b. Adapter's License means the license You apply to Your Copyright and Similar | ||||||
|  | Rights in Your contributions to Adapted Material in accordance with the terms | ||||||
|  | and conditions of this Public License. | ||||||
|  |  | ||||||
|  | c. BY-NC-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, | ||||||
|  | approved by Creative Commons as essentially the equivalent of this Public | ||||||
|  | License. | ||||||
|  |  | ||||||
|  | d. Copyright and Similar Rights means copyright and/or similar rights closely | ||||||
|  | related to copyright including, without limitation, performance, broadcast, | ||||||
|  | sound recording, and Sui Generis Database Rights, without regard to how the | ||||||
|  | rights are labeled or categorized. For purposes of this Public License, the | ||||||
|  | rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. | ||||||
|  |  | ||||||
|  | e. Effective Technological Measures means those measures that, in the absence | ||||||
|  | of proper authority, may not be circumvented under laws fulfilling obligations | ||||||
|  | under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, | ||||||
|  | and/or similar international agreements. | ||||||
|  |  | ||||||
|  | f. Exceptions and Limitations means fair use, fair dealing, and/or any other | ||||||
|  | exception or limitation to Copyright and Similar Rights that applies to Your | ||||||
|  | use of the Licensed Material. | ||||||
|  |  | ||||||
|  | g. License Elements means the license attributes listed in the name of a Creative | ||||||
|  | Commons Public License. The License Elements of this Public License are Attribution, | ||||||
|  | NonCommercial, and ShareAlike. | ||||||
|  |  | ||||||
|  | h. Licensed Material means the artistic or literary work, database, or other | ||||||
|  | material to which the Licensor applied this Public License. | ||||||
|  |  | ||||||
|  | i. Licensed Rights means the rights granted to You subject to the terms and | ||||||
|  | conditions of this Public License, which are limited to all Copyright and | ||||||
|  | Similar Rights that apply to Your use of the Licensed Material and that the | ||||||
|  | Licensor has authority to license. | ||||||
|  |  | ||||||
|  | j. Licensor means the individual(s) or entity(ies) granting rights under this | ||||||
|  | Public License. | ||||||
|  |  | ||||||
|  | k. NonCommercial means not primarily intended for or directed towards commercial | ||||||
|  | advantage or monetary compensation. For purposes of this Public License, the | ||||||
|  | exchange of the Licensed Material for other material subject to Copyright | ||||||
|  | and Similar Rights by digital file-sharing or similar means is NonCommercial | ||||||
|  | provided there is no payment of monetary compensation in connection with the | ||||||
|  | exchange. | ||||||
|  |  | ||||||
|  | l. Share means to provide material to the public by any means or process that | ||||||
|  | requires permission under the Licensed Rights, such as reproduction, public | ||||||
|  | display, public performance, distribution, dissemination, communication, or | ||||||
|  | importation, and to make material available to the public including in ways | ||||||
|  | that members of the public may access the material from a place and at a time | ||||||
|  | individually chosen by them. | ||||||
|  |  | ||||||
|  | m. Sui Generis Database Rights means rights other than copyright resulting | ||||||
|  | from Directive 96/9/EC of the European Parliament and of the Council of 11 | ||||||
|  | March 1996 on the legal protection of databases, as amended and/or succeeded, | ||||||
|  | as well as other essentially equivalent rights anywhere in the world. | ||||||
|  |  | ||||||
|  | n. You means the individual or entity exercising the Licensed Rights under | ||||||
|  | this Public License. Your has a corresponding meaning. | ||||||
|  |  | ||||||
|  | Section 2 – Scope. | ||||||
|  |  | ||||||
|  |    a. License grant. | ||||||
|  |  | ||||||
|  | 1. Subject to the terms and conditions of this Public License, the Licensor | ||||||
|  | hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, | ||||||
|  | irrevocable license to exercise the Licensed Rights in the Licensed Material | ||||||
|  | to: | ||||||
|  |  | ||||||
|  | A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial | ||||||
|  | purposes only; and | ||||||
|  |  | ||||||
|  | B. produce, reproduce, and Share Adapted Material for NonCommercial purposes | ||||||
|  | only. | ||||||
|  |  | ||||||
|  | 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions | ||||||
|  | and Limitations apply to Your use, this Public License does not apply, and | ||||||
|  | You do not need to comply with its terms and conditions. | ||||||
|  |  | ||||||
|  |       3. Term. The term of this Public License is specified in Section 6(a). | ||||||
|  |  | ||||||
|  | 4. Media and formats; technical modifications allowed. The Licensor authorizes | ||||||
|  | You to exercise the Licensed Rights in all media and formats whether now known | ||||||
|  | or hereafter created, and to make technical modifications necessary to do | ||||||
|  | so. The Licensor waives and/or agrees not to assert any right or authority | ||||||
|  | to forbid You from making technical modifications necessary to exercise the | ||||||
|  | Licensed Rights, including technical modifications necessary to circumvent | ||||||
|  | Effective Technological Measures. For purposes of this Public License, simply | ||||||
|  | making modifications authorized by this Section 2(a)(4) never produces Adapted | ||||||
|  | Material. | ||||||
|  |  | ||||||
|  |       5. Downstream recipients. | ||||||
|  |  | ||||||
|  | A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed | ||||||
|  | Material automatically receives an offer from the Licensor to exercise the | ||||||
|  | Licensed Rights under the terms and conditions of this Public License. | ||||||
|  |  | ||||||
|  | B. Additional offer from the Licensor – Adapted Material. Every recipient | ||||||
|  | of Adapted Material from You automatically receives an offer from the Licensor | ||||||
|  | to exercise the Licensed Rights in the Adapted Material under the conditions | ||||||
|  | of the Adapter's License You apply. | ||||||
|  |  | ||||||
|  | C. No downstream restrictions. You may not offer or impose any additional | ||||||
|  | or different terms or conditions on, or apply any Effective Technological | ||||||
|  | Measures to, the Licensed Material if doing so restricts exercise of the Licensed | ||||||
|  | Rights by any recipient of the Licensed Material. | ||||||
|  |  | ||||||
|  | 6. No endorsement. Nothing in this Public License constitutes or may be construed | ||||||
|  | as permission to assert or imply that You are, or that Your use of the Licensed | ||||||
|  | Material is, connected with, or sponsored, endorsed, or granted official status | ||||||
|  | by, the Licensor or others designated to receive attribution as provided in | ||||||
|  | Section 3(a)(1)(A)(i). | ||||||
|  |  | ||||||
|  |    b. Other rights. | ||||||
|  |  | ||||||
|  | 1. Moral rights, such as the right of integrity, are not licensed under this | ||||||
|  | Public License, nor are publicity, privacy, and/or other similar personality | ||||||
|  | rights; however, to the extent possible, the Licensor waives and/or agrees | ||||||
|  | not to assert any such rights held by the Licensor to the limited extent necessary | ||||||
|  | to allow You to exercise the Licensed Rights, but not otherwise. | ||||||
|  |  | ||||||
|  | 2. Patent and trademark rights are not licensed under this Public License. | ||||||
|  |  | ||||||
|  | 3. To the extent possible, the Licensor waives any right to collect royalties | ||||||
|  | from You for the exercise of the Licensed Rights, whether directly or through | ||||||
|  | a collecting society under any voluntary or waivable statutory or compulsory | ||||||
|  | licensing scheme. In all other cases the Licensor expressly reserves any right | ||||||
|  | to collect such royalties, including when the Licensed Material is used other | ||||||
|  | than for NonCommercial purposes. | ||||||
|  |  | ||||||
|  | Section 3 – License Conditions. | ||||||
|  |  | ||||||
|  | Your exercise of the Licensed Rights is expressly made subject to the following | ||||||
|  | conditions. | ||||||
|  |  | ||||||
|  |    a. Attribution. | ||||||
|  |  | ||||||
|  | 1. If You Share the Licensed Material (including in modified form), You must: | ||||||
|  |  | ||||||
|  | A. retain the following if it is supplied by the Licensor with the Licensed | ||||||
|  | Material: | ||||||
|  |  | ||||||
|  | i. identification of the creator(s) of the Licensed Material and any others | ||||||
|  | designated to receive attribution, in any reasonable manner requested by the | ||||||
|  | Licensor (including by pseudonym if designated); | ||||||
|  |  | ||||||
|  |             ii. a copyright notice; | ||||||
|  |  | ||||||
|  |             iii. a notice that refers to this Public License; | ||||||
|  |  | ||||||
|  |             iv. a notice that refers to the disclaimer of warranties; | ||||||
|  |  | ||||||
|  |              | ||||||
|  |  | ||||||
|  | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; | ||||||
|  |  | ||||||
|  | B. indicate if You modified the Licensed Material and retain an indication | ||||||
|  | of any previous modifications; and | ||||||
|  |  | ||||||
|  | C. indicate the Licensed Material is licensed under this Public License, and | ||||||
|  | include the text of, or the URI or hyperlink to, this Public License. | ||||||
|  |  | ||||||
|  | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner | ||||||
|  | based on the medium, means, and context in which You Share the Licensed Material. | ||||||
|  | For example, it may be reasonable to satisfy the conditions by providing a | ||||||
|  | URI or hyperlink to a resource that includes the required information. | ||||||
|  |  | ||||||
|  | 3. If requested by the Licensor, You must remove any of the information required | ||||||
|  | by Section 3(a)(1)(A) to the extent reasonably practicable. | ||||||
|  |  | ||||||
|  | b. ShareAlike.In addition to the conditions in Section 3(a), if You Share | ||||||
|  | Adapted Material You produce, the following conditions also apply. | ||||||
|  |  | ||||||
|  | 1. The Adapter's License You apply must be a Creative Commons license with | ||||||
|  | the same License Elements, this version or later, or a BY-NC-SA Compatible | ||||||
|  | License. | ||||||
|  |  | ||||||
|  | 2. You must include the text of, or the URI or hyperlink to, the Adapter's | ||||||
|  | License You apply. You may satisfy this condition in any reasonable manner | ||||||
|  | based on the medium, means, and context in which You Share Adapted Material. | ||||||
|  |  | ||||||
|  | 3. You may not offer or impose any additional or different terms or conditions | ||||||
|  | on, or apply any Effective Technological Measures to, Adapted Material that | ||||||
|  | restrict exercise of the rights granted under the Adapter's License You apply. | ||||||
|  |  | ||||||
|  | Section 4 – Sui Generis Database Rights. | ||||||
|  |  | ||||||
|  | Where the Licensed Rights include Sui Generis Database Rights that apply to | ||||||
|  | Your use of the Licensed Material: | ||||||
|  |  | ||||||
|  | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, | ||||||
|  | reuse, reproduce, and Share all or a substantial portion of the contents of | ||||||
|  | the database for NonCommercial purposes only; | ||||||
|  |  | ||||||
|  | b. if You include all or a substantial portion of the database contents in | ||||||
|  | a database in which You have Sui Generis Database Rights, then the database | ||||||
|  | in which You have Sui Generis Database Rights (but not its individual contents) | ||||||
|  | is Adapted Material, including for purposes of Section 3(b); and | ||||||
|  |  | ||||||
|  | c. You must comply with the conditions in Section 3(a) if You Share all or | ||||||
|  | a substantial portion of the contents of the database. | ||||||
|  |  | ||||||
|  | For the avoidance of doubt, this Section 4 supplements and does not replace | ||||||
|  | Your obligations under this Public License where the Licensed Rights include | ||||||
|  | other Copyright and Similar Rights. | ||||||
|  |  | ||||||
|  | Section 5 – Disclaimer of Warranties and Limitation of Liability. | ||||||
|  |  | ||||||
|  | a. Unless otherwise separately undertaken by the Licensor, to the extent possible, | ||||||
|  | the Licensor offers the Licensed Material as-is and as-available, and makes | ||||||
|  | no representations or warranties of any kind concerning the Licensed Material, | ||||||
|  | whether express, implied, statutory, or other. This includes, without limitation, | ||||||
|  | warranties of title, merchantability, fitness for a particular purpose, non-infringement, | ||||||
|  | absence of latent or other defects, accuracy, or the presence or absence of | ||||||
|  | errors, whether or not known or discoverable. Where disclaimers of warranties | ||||||
|  | are not allowed in full or in part, this disclaimer may not apply to You. | ||||||
|  |  | ||||||
|  | b. To the extent possible, in no event will the Licensor be liable to You | ||||||
|  | on any legal theory (including, without limitation, negligence) or otherwise | ||||||
|  | for any direct, special, indirect, incidental, consequential, punitive, exemplary, | ||||||
|  | or other losses, costs, expenses, or damages arising out of this Public License | ||||||
|  | or use of the Licensed Material, even if the Licensor has been advised of | ||||||
|  | the possibility of such losses, costs, expenses, or damages. Where a limitation | ||||||
|  | of liability is not allowed in full or in part, this limitation may not apply | ||||||
|  | to You. | ||||||
|  |  | ||||||
|  | c. The disclaimer of warranties and limitation of liability provided above | ||||||
|  | shall be interpreted in a manner that, to the extent possible, most closely | ||||||
|  | approximates an absolute disclaimer and waiver of all liability. | ||||||
|  |  | ||||||
|  | Section 6 – Term and Termination. | ||||||
|  |  | ||||||
|  | a. This Public License applies for the term of the Copyright and Similar Rights | ||||||
|  | licensed here. However, if You fail to comply with this Public License, then | ||||||
|  | Your rights under this Public License terminate automatically. | ||||||
|  |  | ||||||
|  | b. Where Your right to use the Licensed Material has terminated under Section | ||||||
|  | 6(a), it reinstates: | ||||||
|  |  | ||||||
|  | 1. automatically as of the date the violation is cured, provided it is cured | ||||||
|  | within 30 days of Your discovery of the violation; or | ||||||
|  |  | ||||||
|  |       2. upon express reinstatement by the Licensor. | ||||||
|  |  | ||||||
|  | For the avoidance of doubt, this Section 6(b) does not affect any right the | ||||||
|  | Licensor may have to seek remedies for Your violations of this Public License. | ||||||
|  |  | ||||||
|  | c. For the avoidance of doubt, the Licensor may also offer the Licensed Material | ||||||
|  | under separate terms or conditions or stop distributing the Licensed Material | ||||||
|  | at any time; however, doing so will not terminate this Public License. | ||||||
|  |  | ||||||
|  |    d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. | ||||||
|  |  | ||||||
|  | Section 7 – Other Terms and Conditions. | ||||||
|  |  | ||||||
|  | a. The Licensor shall not be bound by any additional or different terms or | ||||||
|  | conditions communicated by You unless expressly agreed. | ||||||
|  |  | ||||||
|  | b. Any arrangements, understandings, or agreements regarding the Licensed | ||||||
|  | Material not stated herein are separate from and independent of the terms | ||||||
|  | and conditions of this Public License. | ||||||
|  |  | ||||||
|  | Section 8 – Interpretation. | ||||||
|  |  | ||||||
|  | a. For the avoidance of doubt, this Public License does not, and shall not | ||||||
|  | be interpreted to, reduce, limit, restrict, or impose conditions on any use | ||||||
|  | of the Licensed Material that could lawfully be made without permission under | ||||||
|  | this Public License. | ||||||
|  |  | ||||||
|  | b. To the extent possible, if any provision of this Public License is deemed | ||||||
|  | unenforceable, it shall be automatically reformed to the minimum extent necessary | ||||||
|  | to make it enforceable. If the provision cannot be reformed, it shall be severed | ||||||
|  | from this Public License without affecting the enforceability of the remaining | ||||||
|  | terms and conditions. | ||||||
|  |  | ||||||
|  | c. No term or condition of this Public License will be waived and no failure | ||||||
|  | to comply consented to unless expressly agreed to by the Licensor. | ||||||
|  |  | ||||||
|  | d. Nothing in this Public License constitutes or may be interpreted as a limitation | ||||||
|  | upon, or waiver of, any privileges and immunities that apply to the Licensor | ||||||
|  | or You, including from the legal processes of any jurisdiction or authority. | ||||||
|  |  | ||||||
|  | Creative Commons is not a party to its public licenses. Notwithstanding, Creative | ||||||
|  | Commons may elect to apply one of its public licenses to material it publishes | ||||||
|  | and in those instances will be considered the "Licensor." The text of the | ||||||
|  | Creative Commons public licenses is dedicated to the public domain under the | ||||||
|  | CC0 Public Domain Dedication. Except for the limited purpose of indicating | ||||||
|  | that material is shared under a Creative Commons public license or as otherwise | ||||||
|  | permitted by the Creative Commons policies published at creativecommons.org/policies, | ||||||
|  | Creative Commons does not authorize the use of the trademark "Creative Commons" | ||||||
|  | or any other trademark or logo of Creative Commons without its prior written | ||||||
|  | consent including, without limitation, in connection with any unauthorized | ||||||
|  | modifications to any of its public licenses or any other arrangements, understandings, | ||||||
|  | or agreements concerning use of licensed material. For the avoidance of doubt, | ||||||
|  | this paragraph does not form part of the public licenses. | ||||||
|  |  | ||||||
|  | Creative Commons may be contacted at creativecommons.org. | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| const config = { | const config = { | ||||||
| 	baseurl: 'https://dev.lauf-fuer-kaya.de', | 	baseurl: 'https://dev.lauf-fuer-kaya.de', | ||||||
|  | 	documentserver_key: 'NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe', | ||||||
| 	fallback_username: 'admin', | 	fallback_username: 'admin', | ||||||
| 	fallback_password: '72fpTzsev4xUu78QPs2FCbwZ3', | 	fallback_password: '72fpTzsev4xUu78QPs2FCbwZ3', | ||||||
| 	prefersHashRouting: true | 	prefersHashRouting: true | ||||||
|   | |||||||
| @@ -1,23 +1,23 @@ | |||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html lang="en"> | <html lang="en"> | ||||||
|  |  | ||||||
| <head> | <head> | ||||||
|   <meta charset="utf-8" /> |   <meta charset="utf-8" /> | ||||||
|   <link rel="icon" href="/favicon.png" /> |   <link rel="icon" href="/favicon.png" /> | ||||||
|   <link rel="manifest" href="/manifest.webmanifest"> |   <link rel="manifest" href="/manifest.webmanifest"> | ||||||
|   <link rel="apple-touch-icon" href="/lfk-logo.png"> |   <link rel="apple-touch-icon" href="/lfk-logo.png"> | ||||||
|   <meta name="theme-color" content="#FFFFFF"> |   <meta name="theme-color" content="#FFFFFF"> | ||||||
|   <meta name="viewport" content="width=device-width, initial-scale=1" /> |   <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||||
|   <meta name="description" content="Lauf Für Kaya! - Admin" /> |   <meta name="description" content="Lauf Für Kaya! - Admin" /> | ||||||
|   <title>Lauf für Kaya! - Admin</title> |   <title>Lauf für Kaya! - Admin</title> | ||||||
|   __TAILWIND_INSERT__ |   __TAILWIND_INSERT__ | ||||||
| </head> | </head> | ||||||
|  |  | ||||||
| <body> | <body> | ||||||
|   <span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.6.0-RELEASE_INFO</span> |   <span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.8.3-RELEASE_INFO</span> | ||||||
|   <noscript>You need to enable JavaScript to run this app.</noscript> |   <noscript>You need to enable JavaScript to run this app.</noscript> | ||||||
|   <script src="/env.js"></script> |   <script src="/env.js"></script> | ||||||
|   <script defer type="module" src="/_dist_/index.js"></script> |   <script defer type="module" src="/_dist_/index.js"></script> | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
| </html> | </html> | ||||||
							
								
								
									
										124
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,63 +1,61 @@ | |||||||
| { | { | ||||||
| 	"name": "@odit/lfk-frontend", | 	"name": "@odit/lfk-frontend", | ||||||
| 	"version": "0.6.0", | 	"version": "0.8.3", | ||||||
| 	"scripts": { | 	"scripts": { | ||||||
| 		"i18n-order": "node order.js", | 		"i18n-order": "node order.js", | ||||||
| 		"dev:all": "yarn prebuild && snowpack dev", | 		"dev:all": "yarn prebuild && snowpack dev", | ||||||
| 		"dev": "cross-env NODE_ENV_ODIT=development_fast node template-copy.js && yarn build:sw && snowpack dev", | 		"dev": "cross-env NODE_ENV_ODIT=development_fast node template-copy.js && yarn build:sw && snowpack dev", | ||||||
| 		"build": "yarn prebuild && snowpack build", | 		"build": "yarn prebuild && snowpack build", | ||||||
| 		"prebuild": "cross-env NODE_ENV_ODIT=production node template-copy.js && yarn build:sw", | 		"prebuild": "cross-env NODE_ENV_ODIT=production node template-copy.js && yarn build:sw", | ||||||
| 		"build:sw": "workbox generateSW workbox-config.js", | 		"build:sw": "workbox generateSW workbox-config.js", | ||||||
| 		"release": "release-it", | 		"release": "release-it", | ||||||
| 		"licenses:export": "license-exporter --json -o public" | 		"licenses:export": "license-exporter --json -o public" | ||||||
| 	}, | 	}, | ||||||
| 	"dependencies": { | 	"license": "CC-BY-NC-SA-4.0", | ||||||
| 		"@odit/lfk-client-js": "0.4.5", | 	"dependencies": { | ||||||
| 		"csvtojson": "^2.0.10", | 		"@odit/lfk-client-js": "0.6.4", | ||||||
| 		"filepond": "4.25.1", | 		"csvtojson": "^2.0.10", | ||||||
| 		"gridjs": "3.3.0", | 		"gridjs": "3.3.0", | ||||||
| 		"localforage": "1.9.0", | 		"localforage": "1.9.0", | ||||||
| 		"lodash.isequal": "^4.5.0", | 		"marked": "^2.0.1", | ||||||
| 		"marked": "^2.0.0", | 		"svelte-focus-trap": "1.0.1", | ||||||
| 		"svelte-filepond": "0.0.1", | 		"svelte-i18n": "3.3.7", | ||||||
| 		"svelte-focus-trap": "1.0.1", | 		"svelte-select": "^3.17.0", | ||||||
| 		"svelte-i18n": "3.3.2", | 		"tailwindcss": "2.0.3", | ||||||
| 		"svelte-select": "^3.16.1", | 		"tinro": "0.6.1", | ||||||
| 		"tailwindcss": "2.0.3", | 		"toastify-js": "1.9.3", | ||||||
| 		"tinro": "0.5.12", | 		"validator": "13.5.2", | ||||||
| 		"toastify-js": "1.9.3", | 		"xlsx": "^0.16.9" | ||||||
| 		"validator": "13.5.2", | 	}, | ||||||
| 		"xlsx": "^0.16.9" | 	"devDependencies": { | ||||||
| 	}, | 		"@odit/license-exporter": "^0.0.11", | ||||||
| 	"devDependencies": { | 		"@snowpack/plugin-svelte": "3.5.2", | ||||||
| 		"@odit/license-exporter": "0.0.9", | 		"auto-changelog": "^2.2.1", | ||||||
| 		"@snowpack/plugin-svelte": "3.5.2", | 		"autoprefixer": "10.2.5", | ||||||
| 		"auto-changelog": "^2.2.1", | 		"cross-env": "^7.0.3", | ||||||
| 		"autoprefixer": "10.2.4", | 		"postcss": "8.2.8", | ||||||
| 		"cross-env": "^7.0.3", | 		"postcss-load-config": "3.0.1", | ||||||
| 		"postcss": "8.2.6", | 		"release-it": "^14.4.1", | ||||||
| 		"postcss-load-config": "3.0.1", | 		"snowpack": "3.0.13", | ||||||
| 		"release-it": "^14.4.0", | 		"svelte": "3.35.0", | ||||||
| 		"snowpack": "3.0.11", | 		"svelte-preprocess": "4.6.9", | ||||||
| 		"svelte": "3.32.3", | 		"workbox-cli": "6.1.2" | ||||||
| 		"svelte-preprocess": "4.6.8", | 	}, | ||||||
| 		"workbox-cli": "6.1.0" | 	"release-it": { | ||||||
| 	}, | 		"git": { | ||||||
| 	"release-it": { | 			"commit": true, | ||||||
| 		"git": { | 			"requireCleanWorkingDir": false, | ||||||
| 			"commit": true, | 			"commitMessage": "🚀RELEASE v${version}", | ||||||
| 			"requireCleanWorkingDir": false, | 			"push": false, | ||||||
| 			"commitMessage": "🚀RELEASE v${version}", | 			"tag": true, | ||||||
| 			"push": false, | 			"tagName": null, | ||||||
| 			"tag": true, | 			"tagAnnotation": "v${version}" | ||||||
| 			"tagName": null, | 		}, | ||||||
| 			"tagAnnotation": "v${version}" | 		"npm": { | ||||||
| 		}, | 			"publish": false | ||||||
| 		"npm": { | 		}, | ||||||
| 			"publish": false | 		"hooks": { | ||||||
| 		}, | 			"after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js  && git add index.template.html && node order.js  && git add src/locales" | ||||||
| 		"hooks": { | 		} | ||||||
| 			"after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js  && git add index.template.html && node order.js  && git add src/locales" | 	} | ||||||
| 		} | } | ||||||
| 	} |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| const config = { | const config = { | ||||||
| 	baseurl: 'http://localhost:4010', | 	baseurl: 'http://localhost:4010', | ||||||
|  | 	documentserver_key: 'NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe' | ||||||
| 	// optional | 	// optional | ||||||
| 	prefersHashRouting: true | 	prefersHashRouting: true | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										116
									
								
								src/App.svelte
									
									
									
									
									
								
							
							
						
						| @@ -27,32 +27,53 @@ | |||||||
|     storeName: "lfk_admin", |     storeName: "lfk_admin", | ||||||
|     description: "LfK! admin dashbaord", |     description: "LfK! admin dashbaord", | ||||||
|   }); |   }); | ||||||
|  |   window.onunhandledrejection = (event) => { | ||||||
|  |     if (event.reason.toString() == "Error: Unauthorized") { | ||||||
|  |       console.log("Found 1"); | ||||||
|  |       localForage.clear(); | ||||||
|  |       location.replace("/"); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|   // |   // | ||||||
|   import Login from "./components/Login.svelte"; |   import Login from "./components/auth/Login.svelte"; | ||||||
|   import Dashboard from "./components/Dashboard.svelte"; |   import Dashboard from "./components/dashboard/Dashboard.svelte"; | ||||||
|   import store from "./store.js"; |   import store from "./store.js"; | ||||||
|   import ForgotPassword from "./components/ForgotPassword.svelte"; |   import ForgotPassword from "./components/auth/ForgotPassword.svelte"; | ||||||
|   import MainDashContent from "./components/MainDashContent.svelte"; |   import MainDashContent from "./components/dashboard/MainDashContent.svelte"; | ||||||
|   import Users from "./components/Users.svelte"; |   import Users from "./components/users/Users.svelte"; | ||||||
|   import About from "./components/About.svelte"; |   import About from "./components/general/About.svelte"; | ||||||
|   import Settings from "./components/Settings.svelte"; |   import Settings from "./components/settings/Settings.svelte"; | ||||||
|   import Transition from "./components/Transition.svelte"; |   import Transition from "./components/base/Transition.svelte"; | ||||||
|   import Orgs from "./components/Orgs.svelte"; |   import Orgs from "./components/orgs/Orgs.svelte"; | ||||||
|   import Runners from "./components/Runners.svelte"; |   import Runners from "./components/runners/Runners.svelte"; | ||||||
|   import Footer from "./components/Footer.svelte"; |   import Footer from "./components/general/Footer.svelte"; | ||||||
|   import TracksOverview from "./components/TracksOverview.svelte"; |   import TracksOverview from "./components/tracks/TracksOverview.svelte"; | ||||||
|   import OrgDetail from "./components/OrgDetail.svelte"; |   import OrgDetail from "./components/orgs/OrgDetail.svelte"; | ||||||
|   import Teams from "./components/Teams.svelte"; |   import Teams from "./components/teams/Teams.svelte"; | ||||||
|   import { OpenAPI } from "@odit/lfk-client-js"; |   import { OpenAPI } from "@odit/lfk-client-js"; | ||||||
|   import UserDetail from "./components/UserDetail.svelte"; |   import UserDetail from "./components/users/UserDetail.svelte"; | ||||||
|   OpenAPI.BASE = config.baseurl; |   OpenAPI.BASE = config.baseurl; | ||||||
|   import { register as registerSW } from "./swmodule"; |   import { register as registerSW } from "./swmodule"; | ||||||
|   import TeamDetail from "./components/TeamDetail.svelte"; |   import TeamDetail from "./components/teams/TeamDetail.svelte"; | ||||||
|   import UserPermissions from "./components/UserPermissions.svelte"; |   import UserPermissions from "./components/users/UserPermissions.svelte"; | ||||||
|   import RunnerDetail from "./components/RunnerDetail.svelte"; |   import GroupPermissions from "./components/groups/GroupPermissions.svelte"; | ||||||
|   import Imprint from "./components/Imprint.svelte"; |   import RunnerDetail from "./components/runners/RunnerDetail.svelte"; | ||||||
|   import Privacy from "./components/Privacy.svelte"; |   import Imprint from "./components/general/Imprint.svelte"; | ||||||
| import ResetPassword from "./components/ResetPassword.svelte"; |   import Privacy from "./components/general/Privacy.svelte"; | ||||||
|  |   import ResetPassword from "./components/auth/ResetPassword.svelte"; | ||||||
|  |   import Contacts from "./components/contacts/Contacts.svelte"; | ||||||
|  |   import ContactDetail from "./components/contacts/ContactDetail.svelte"; | ||||||
|  |   import Donors from "./components/donors/Donors.svelte"; | ||||||
|  |   import Groups from "./components/groups/Groups.svelte"; | ||||||
|  |   import DonorDetail from "./components/donors/DonorDetail.svelte"; | ||||||
|  |   import Donations from "./components/donations/Donations.svelte"; | ||||||
|  |   import DonationDetail from "./components/donations/DonationDetail.svelte"; | ||||||
|  |   import GroupDetail from "./components/groups/GroupDetail.svelte"; | ||||||
|  |   import ScanStationsOverview from "./components/scanstations/ScanStationsOverview.svelte"; | ||||||
|  |   import ScanStations from "./components/scanstations/ScanStations.svelte"; | ||||||
|  |   import ScanStationDetail from "./components/scanstations/ScanStationDetail.svelte"; | ||||||
|  |   import Scans from "./components/scans/Scans.svelte"; | ||||||
|  | import ScanDetail from "./components/scans/ScanDetail.svelte"; | ||||||
|   store.init(); |   store.init(); | ||||||
|   registerSW(); |   registerSW(); | ||||||
| </script> | </script> | ||||||
| @@ -97,6 +118,19 @@ import ResetPassword from "./components/ResetPassword.svelte"; | |||||||
|             </Route> |             </Route> | ||||||
|           </Route> |           </Route> | ||||||
|         </Route> |         </Route> | ||||||
|  |         <Route path="/groups/*"> | ||||||
|  |           <Route path="/"> | ||||||
|  |             <Groups /> | ||||||
|  |           </Route> | ||||||
|  |           <Route path="/:groupid/*" let:params> | ||||||
|  |             <Route path="/"> | ||||||
|  |               <GroupDetail {params} /> | ||||||
|  |             </Route> | ||||||
|  |             <Route path="/permissions/"> | ||||||
|  |               <GroupPermissions {params} /> | ||||||
|  |             </Route> | ||||||
|  |           </Route> | ||||||
|  |         </Route> | ||||||
|         <Route path="/tracks/*"> |         <Route path="/tracks/*"> | ||||||
|           <Route path="/"> |           <Route path="/"> | ||||||
|             <TracksOverview /> |             <TracksOverview /> | ||||||
| @@ -119,6 +153,14 @@ import ResetPassword from "./components/ResetPassword.svelte"; | |||||||
|             <TeamDetail {params} /> |             <TeamDetail {params} /> | ||||||
|           </Route> |           </Route> | ||||||
|         </Route> |         </Route> | ||||||
|  |         <Route path="/contacts/*"> | ||||||
|  |           <Route path="/"> | ||||||
|  |             <Contacts /> | ||||||
|  |           </Route> | ||||||
|  |           <Route path="/:contact" let:params> | ||||||
|  |             <ContactDetail {params} /> | ||||||
|  |           </Route> | ||||||
|  |         </Route> | ||||||
|         <Route path="/orgs/*"> |         <Route path="/orgs/*"> | ||||||
|           <Route path="/"> |           <Route path="/"> | ||||||
|             <Orgs /> |             <Orgs /> | ||||||
| @@ -127,6 +169,38 @@ import ResetPassword from "./components/ResetPassword.svelte"; | |||||||
|             <OrgDetail {params} /> |             <OrgDetail {params} /> | ||||||
|           </Route> |           </Route> | ||||||
|         </Route> |         </Route> | ||||||
|  |         <Route path="/donors/*"> | ||||||
|  |           <Route path="/"> | ||||||
|  |             <Donors /> | ||||||
|  |           </Route> | ||||||
|  |           <Route path="/:donorid" let:params> | ||||||
|  |             <DonorDetail {params} /> | ||||||
|  |           </Route> | ||||||
|  |         </Route> | ||||||
|  |         <Route path="/donations/*"> | ||||||
|  |           <Route path="/"> | ||||||
|  |             <Donations /> | ||||||
|  |           </Route> | ||||||
|  |           <Route path="/:donationid" let:params> | ||||||
|  |             <DonationDetail {params} /> | ||||||
|  |           </Route> | ||||||
|  |         </Route> | ||||||
|  |         <Route path="/scans/*"> | ||||||
|  |           <Route path="/"> | ||||||
|  |             <Scans /> | ||||||
|  |           </Route> | ||||||
|  |           <Route path="/:scanid" let:params> | ||||||
|  |             <ScanDetail {params} /> | ||||||
|  |           </Route> | ||||||
|  |         </Route> | ||||||
|  |         <Route path="/scanstations/*"> | ||||||
|  |           <Route path="/"> | ||||||
|  |             <ScanStations /> | ||||||
|  |           </Route> | ||||||
|  |           <Route path="/:stationid" let:params> | ||||||
|  |             <ScanStationDetail {params} /> | ||||||
|  |           </Route> | ||||||
|  |         </Route> | ||||||
|         <Route path="/about"> |         <Route path="/about"> | ||||||
|           <About /> |           <About /> | ||||||
|         </Route> |         </Route> | ||||||
|   | |||||||
| @@ -1,74 +0,0 @@ | |||||||
| <script> |  | ||||||
|   import "filepond/dist/filepond.css"; |  | ||||||
|   import FilePond from "svelte-filepond"; |  | ||||||
|   import { _ } from "svelte-i18n"; |  | ||||||
|   let pond; |  | ||||||
|   // pond.getFiles() will return the active files |  | ||||||
|   // the name to use for the internal file input |  | ||||||
|   let name = "filepond"; |  | ||||||
|   function handleInit() { |  | ||||||
|     // console.log("FilePond has initialised"); |  | ||||||
|   } |  | ||||||
|   function handleAddFile(err, fileItem) { |  | ||||||
|     // console.log("A file has been added", fileItem); |  | ||||||
|   } |  | ||||||
|   const labelInvalidField = $_("filepond__field-contains-invalid-files"); |  | ||||||
|   const labelFileWaitingForSize = $_("filepond__waiting-for-size"); |  | ||||||
|   const labelFileSizeNotAvailable = $_("filepond__size-not-available"); |  | ||||||
|   const labelFileLoading = $_("filepond__loading"); |  | ||||||
|   const labelFileLoadError = $_("filepond__error-during-load"); |  | ||||||
|   const labelFileProcessing = $_("filepond__uploading"); |  | ||||||
|   const labelFileProcessingComplete = $_("filepond__upload-complete"); |  | ||||||
|   const labelFileProcessingAborted = $_("filepond__upload-cancelled"); |  | ||||||
|   const labelFileProcessingError = $_("filepond__error-during-upload"); |  | ||||||
|   const labelFileProcessingRevertError = $_("filepond__error-during-revert"); |  | ||||||
|   const labelFileRemoveError = $_("filepond__error-during-remove"); |  | ||||||
|   const labelTapToCancel = $_("filepond__tap-to-cancel"); |  | ||||||
|   const labelTapToRetry = $_("filepond__tap-to-retry"); |  | ||||||
|   const labelTapToUndo = $_("filepond__tap-to-undo"); |  | ||||||
|   const labelButtonRemoveItem = $_("filepond__remove"); |  | ||||||
|   const labelButtonAbortItemLoad = $_("filepond__abort"); |  | ||||||
|   const labelButtonRetryItemLoad = $_("filepond__retry"); |  | ||||||
|   const labelButtonAbortItemProcessing = $_("filepond__cancel"); |  | ||||||
|   const labelButtonUndoItemProcessing = $_("filepond__undo"); |  | ||||||
|   const labelButtonRetryItemProcessing = $_("filepond__retry"); |  | ||||||
|   const labelButtonProcessItem = $_("filepond__upload"); |  | ||||||
|   const labelIdle = |  | ||||||
|     $_("drag-and-drop-your-files-or") + |  | ||||||
|     ` <span class="filepond--label-action"> ` + |  | ||||||
|     $_("browse") + |  | ||||||
|     ` </span>`; |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <div class="app"> |  | ||||||
|   <FilePond |  | ||||||
|     bind:this={pond} |  | ||||||
|     {name} |  | ||||||
|     {labelFileWaitingForSize} |  | ||||||
|     {labelFileSizeNotAvailable} |  | ||||||
|     {labelFileLoading} |  | ||||||
|     {labelFileLoadError} |  | ||||||
|     {labelFileProcessing} |  | ||||||
|     {labelFileProcessingComplete} |  | ||||||
|     {labelFileProcessingAborted} |  | ||||||
|     {labelFileProcessingError} |  | ||||||
|     {labelFileProcessingRevertError} |  | ||||||
|     {labelFileRemoveError} |  | ||||||
|     {labelTapToCancel} |  | ||||||
|     {labelTapToRetry} |  | ||||||
|     {labelTapToUndo} |  | ||||||
|     {labelButtonRemoveItem} |  | ||||||
|     {labelButtonAbortItemLoad} |  | ||||||
|     {labelButtonRetryItemLoad} |  | ||||||
|     {labelButtonAbortItemProcessing} |  | ||||||
|     {labelButtonUndoItemProcessing} |  | ||||||
|     {labelButtonRetryItemProcessing} |  | ||||||
|     {labelButtonProcessItem} |  | ||||||
|     {labelIdle} |  | ||||||
|     {labelInvalidField} |  | ||||||
|     server="/api" |  | ||||||
|     allowMultiple={false} |  | ||||||
|     credits={false} |  | ||||||
|     oninit={handleInit} |  | ||||||
|     onaddfile={handleAddFile} /> |  | ||||||
| </div> |  | ||||||
| @@ -1,239 +0,0 @@ | |||||||
| <script> |  | ||||||
|   import { RunnerOrganizationService } from "@odit/lfk-client-js"; |  | ||||||
|   import { _ } from "svelte-i18n"; |  | ||||||
|   import Toastify from "toastify-js"; |  | ||||||
|   import store from "../store"; |  | ||||||
|   import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte"; |  | ||||||
|   import ImportRunnerModal from "./ImportRunnerModal.svelte"; |  | ||||||
|   import PromiseError from "./PromiseError.svelte"; |  | ||||||
|   $: delete_triggered = false; |  | ||||||
|   $: save_enabled = !data_changed; |  | ||||||
|   export let params; |  | ||||||
|   let orgdata = {}; |  | ||||||
|   let original = {}; |  | ||||||
|   $: data_loaded = false; |  | ||||||
|   $: data_changed = JSON.stringify(orgdata) === JSON.stringify(original); |  | ||||||
|   const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne( |  | ||||||
|     params.orgid |  | ||||||
|   ).then((value) => { |  | ||||||
|     data_loaded = true; |  | ||||||
|     orgdata = Object.assign(orgdata, value); |  | ||||||
|     original = Object.assign(original, value); |  | ||||||
|   }); |  | ||||||
|   let modal_open = false; |  | ||||||
|   let delete_org = {}; |  | ||||||
|   function deleteOrganization() { |  | ||||||
|     // RunnerOrganizationService.runnerOrganizationControllerRemove( |  | ||||||
|     //   original.id, |  | ||||||
|     //   false |  | ||||||
|     // ) |  | ||||||
|     //   .then((resp) => { |  | ||||||
|     //     Toastify({ |  | ||||||
|     //       text: "Organization deleted", |  | ||||||
|     //       duration: 500, |  | ||||||
|     //       backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", |  | ||||||
|     //     }).showToast(); |  | ||||||
|     //     location.replace("./"); |  | ||||||
|     //   }) |  | ||||||
|     //   .catch((err) => { |  | ||||||
|     modal_open = true; |  | ||||||
|     delete_org = original; |  | ||||||
|     // }); |  | ||||||
|   } |  | ||||||
|   function submit() { |  | ||||||
|     if (data_loaded === true && save_enabled) { |  | ||||||
|       Toastify({ |  | ||||||
|         text: "updating organization", |  | ||||||
|         duration: 2500, |  | ||||||
|       }).showToast(); |  | ||||||
|       RunnerOrganizationService.runnerOrganizationControllerPut( |  | ||||||
|         original.id, |  | ||||||
|         orgdata |  | ||||||
|       ) |  | ||||||
|         .then((resp) => { |  | ||||||
|           Object.assign(original, orgdata); |  | ||||||
|           original = orgdata; |  | ||||||
|           Object.assign(original, orgdata); |  | ||||||
|           // |  | ||||||
|           Toastify({ |  | ||||||
|             text: "updated organization", |  | ||||||
|             duration: 2500, |  | ||||||
|             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", |  | ||||||
|           }).showToast(); |  | ||||||
|         }) |  | ||||||
|         .catch((err) => {}); |  | ||||||
|     } else { |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   export let import_modal_open = false; |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <ImportRunnerModal |  | ||||||
|   on:cancelDelete={(event) => { |  | ||||||
|     import_modal_open = false; |  | ||||||
|   }} |  | ||||||
|   current_runners={[]} |  | ||||||
|   passed_team={{}} |  | ||||||
|   passed_orgs={[]} |  | ||||||
|   passed_org={orgdata} |  | ||||||
|   opened_from="OrgDetail" |  | ||||||
|   bind:import_modal_open /> |  | ||||||
| <ConfirmOrgDeletion bind:modal_open bind:delete_org /> |  | ||||||
| {#if data_loaded} |  | ||||||
|   <section class="container p-5"> |  | ||||||
|     <div class="mb-8 text-3xl font-extrabold leading-tight"> |  | ||||||
|       {original.name} |  | ||||||
|       <span data-id="org_actions_${orgdata.id}"> |  | ||||||
|         {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} |  | ||||||
|           <button |  | ||||||
|             on:click={() => { |  | ||||||
|               import_modal_open = true; |  | ||||||
|             }} |  | ||||||
|             type="button" |  | ||||||
|             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> |  | ||||||
|             {$_('import-runners')} |  | ||||||
|           </button> |  | ||||||
|         {/if} |  | ||||||
|         {#if store.state.jwtinfo.userdetails.permissions.includes('USER:DELETE')} |  | ||||||
|           {#if delete_triggered} |  | ||||||
|             <button |  | ||||||
|               on:click={deleteOrganization} |  | ||||||
|               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button> |  | ||||||
|             <button |  | ||||||
|               on:click={() => { |  | ||||||
|                 delete_triggered = !delete_triggered; |  | ||||||
|               }} |  | ||||||
|               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> |  | ||||||
|           {/if} |  | ||||||
|           {#if !delete_triggered} |  | ||||||
|             <button |  | ||||||
|               on:click={() => { |  | ||||||
|                 delete_triggered = true; |  | ||||||
|               }} |  | ||||||
|               type="button" |  | ||||||
|               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-organization')}</button> |  | ||||||
|           {/if} |  | ||||||
|         {/if} |  | ||||||
|         {#if !delete_triggered} |  | ||||||
|           <button |  | ||||||
|             on:click={submit} |  | ||||||
|             disabled={!save_enabled} |  | ||||||
|             class:opacity-50={!save_enabled} |  | ||||||
|             type="button" |  | ||||||
|             class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> |  | ||||||
|         {/if} |  | ||||||
|       </span> |  | ||||||
|     </div> |  | ||||||
|     <div class="flex flex-row mb-4"> |  | ||||||
|       <div class="w-full"> |  | ||||||
|         <nav class="w-full flex"> |  | ||||||
|           <ol class="list-none flex flex-row items-center justify-start"> |  | ||||||
|             <li class="mr-2 flex items-center"> |  | ||||||
|               <svg |  | ||||||
|                 stroke="currentColor" |  | ||||||
|                 fill="none" |  | ||||||
|                 stroke-width="2" |  | ||||||
|                 viewBox="0 0 24 24" |  | ||||||
|                 stroke-linecap="round" |  | ||||||
|                 stroke-linejoin="round" |  | ||||||
|                 class="h-3 w-3 stroke-current" |  | ||||||
|                 height="1em" |  | ||||||
|                 width="1em" |  | ||||||
|                 xmlns="http://www.w3.org/2000/svg"><path |  | ||||||
|                   d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> |  | ||||||
|                 <polyline points="9 22 9 12 15 12 15 22" /></svg> |  | ||||||
|             </li> |  | ||||||
|             <li class="flex items-center"> |  | ||||||
|               <a class="mr-2" href="/">Home</a><svg |  | ||||||
|                 stroke="currentColor" |  | ||||||
|                 fill="none" |  | ||||||
|                 stroke-width="2" |  | ||||||
|                 viewBox="0 0 24 24" |  | ||||||
|                 stroke-linecap="round" |  | ||||||
|                 stroke-linejoin="round" |  | ||||||
|                 class="h-3 w-3 mr-2 stroke-current" |  | ||||||
|                 height="1em" |  | ||||||
|                 width="1em" |  | ||||||
|                 xmlns="http://www.w3.org/2000/svg"><line |  | ||||||
|                   x1="5" |  | ||||||
|                   y1="12" |  | ||||||
|                   x2="19" |  | ||||||
|                   y2="12" /> |  | ||||||
|                 <polyline points="12 5 19 12 12 19" /></svg> |  | ||||||
|             </li> |  | ||||||
|             <li class="mr-2 flex items-center"> |  | ||||||
|               <svg |  | ||||||
|                 xmlns="http://www.w3.org/2000/svg" |  | ||||||
|                 viewBox="0 0 24 24" |  | ||||||
|                 width="24" |  | ||||||
|                 height="24"><path fill="none" d="M0 0h24v24H0z" /> |  | ||||||
|                 <path |  | ||||||
|                   d="M21 20h2v2H1v-2h2V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v17zm-2 0V4H5v16h14zM8 11h3v2H8v-2zm0-4h3v2H8V7zm0 8h3v2H8v-2zm5 0h3v2h-3v-2zm0-4h3v2h-3v-2zm0-4h3v2h-3V7z" /></svg> |  | ||||||
|             </li> |  | ||||||
|             <li class="flex items-center"> |  | ||||||
|               <a class="mr-2" href="./">Orgs</a><svg |  | ||||||
|                 stroke="currentColor" |  | ||||||
|                 fill="none" |  | ||||||
|                 stroke-width="2" |  | ||||||
|                 viewBox="0 0 24 24" |  | ||||||
|                 stroke-linecap="round" |  | ||||||
|                 stroke-linejoin="round" |  | ||||||
|                 class="h-3 w-3 mr-2 stroke-current" |  | ||||||
|                 height="1em" |  | ||||||
|                 width="1em" |  | ||||||
|                 xmlns="http://www.w3.org/2000/svg"><line |  | ||||||
|                   x1="5" |  | ||||||
|                   y1="12" |  | ||||||
|                   x2="19" |  | ||||||
|                   y2="12" /> |  | ||||||
|                 <polyline points="12 5 19 12 12 19" /></svg> |  | ||||||
|             </li> |  | ||||||
|             <li class="flex items-center"> |  | ||||||
|               <span class="mr-2">Org-Details #{params.orgid}</span> |  | ||||||
|             </li> |  | ||||||
|           </ol> |  | ||||||
|         </nav> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|     <div class="text-sm w-full"> |  | ||||||
|       <label for="name" class="font-medium text-gray-700">Name</label> |  | ||||||
|       <input |  | ||||||
|         autocomplete="off" |  | ||||||
|         placeholder="Name" |  | ||||||
|         type="text" |  | ||||||
|         bind:value={orgdata.name} |  | ||||||
|         name="name" |  | ||||||
|         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> |  | ||||||
|     </div> |  | ||||||
|     <div class="text-sm w-full"> |  | ||||||
|       <label |  | ||||||
|         for="contact" |  | ||||||
|         class="font-medium text-gray-700">{$_('contact')}</label> |  | ||||||
|       <input |  | ||||||
|         autocomplete="off" |  | ||||||
|         placeholder={$_('contact')} |  | ||||||
|         type="text" |  | ||||||
|         bind:value={orgdata.contact} |  | ||||||
|         name="contact" |  | ||||||
|         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> |  | ||||||
|     </div> |  | ||||||
|     <div class="text-sm w-full"> |  | ||||||
|       <label |  | ||||||
|         for="address" |  | ||||||
|         class="font-medium text-gray-700">{$_('address')}</label> |  | ||||||
|       <input |  | ||||||
|         autocomplete="off" |  | ||||||
|         placeholder={$_('address')} |  | ||||||
|         type="text" |  | ||||||
|         bind:value={orgdata.address} |  | ||||||
|         name="address" |  | ||||||
|         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> |  | ||||||
|     </div> |  | ||||||
|   </section> |  | ||||||
| {:else} |  | ||||||
|   {#await promise} |  | ||||||
|     organization detail is being loaded... |  | ||||||
|   {:catch error} |  | ||||||
|     <PromiseError /> |  | ||||||
|   {/await} |  | ||||||
| {/if} |  | ||||||
| @@ -1,187 +0,0 @@ | |||||||
| <script> |  | ||||||
|   import { _ } from "svelte-i18n"; |  | ||||||
|   import { RunnerService,RunnerTeamService, |  | ||||||
|     RunnerOrganizationService, } from "@odit/lfk-client-js"; |  | ||||||
|   import store from "../store"; |  | ||||||
|   import RunnersEmptyState from "./RunnersEmptyState.svelte"; |  | ||||||
|   import Select from 'svelte-select'; |  | ||||||
|   $: searchvalue = ""; |  | ||||||
|   $: active_deletes = []; |  | ||||||
|   export let current_runners = []; |  | ||||||
|   const runners_promise = RunnerService.runnerControllerGetAll().then((val) => { |  | ||||||
|     current_runners = val; |  | ||||||
|   }); |  | ||||||
|   $: selectedFilter_teams = null; |  | ||||||
|   $: selectedFilter = null; |  | ||||||
|   $: filter__teams = selectedFilter_teams||[]; |  | ||||||
|   $: filter__orgs = selectedFilter||[]; |  | ||||||
|   $:filterGroupIDs=filter__teams.concat(filter__orgs).map(i=>i.value) |  | ||||||
|   $: teams = []; |  | ||||||
|   $: orgs = []; |  | ||||||
|   $:mappedteams=teams.map(function(g){ |  | ||||||
|     return {value:g.id,label:g.parentGroup.name+" > "+g.name} |  | ||||||
|   }) |  | ||||||
|   $:selectgroups=(orgs.map(function(g){ |  | ||||||
|     return {value:g.id,label:g.name} |  | ||||||
|   })).concat(mappedteams) |  | ||||||
|   RunnerTeamService.runnerTeamControllerGetAll().then((val) => { |  | ||||||
|     teams = val; |  | ||||||
|   }); |  | ||||||
|   RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { |  | ||||||
|     orgs = val; |  | ||||||
|   }); |  | ||||||
|   function should_display_based_on_id(id) { |  | ||||||
|     if(searchvalue.toString().slice(-1)==="*"){ |  | ||||||
|       return id.toString().startsWith(searchvalue.replace("*","")) |  | ||||||
|     } |  | ||||||
|     return id.toString()===searchvalue; |  | ||||||
|   } |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} |  | ||||||
|   {#await runners_promise} |  | ||||||
|     <div |  | ||||||
|       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" |  | ||||||
|       role="alert"> |  | ||||||
|       <p class="font-bold">runners are being loaded...</p> |  | ||||||
|       <p class="text-sm">{$_('this-might-take-a-moment')}</p> |  | ||||||
|     </div> |  | ||||||
|   {:then} |  | ||||||
|     {#if current_runners.length === 0} |  | ||||||
|       <RunnersEmptyState /> |  | ||||||
|     {:else} |  | ||||||
|       <input |  | ||||||
|         type="search" |  | ||||||
|         bind:value={searchvalue} |  | ||||||
|         placeholder={$_('datatable.search')} |  | ||||||
|         aria-label={$_('datatable.search')} |  | ||||||
|         class="gridjs-input gridjs-search-input mb-4" /> |  | ||||||
|       <div class="block mb-1"> |  | ||||||
|         <label for="country" class="text-sm font-medium text-gray-700">Filter by Organization/ Team</label> |  | ||||||
|         <Select on:select={(event)=>{ |  | ||||||
|           selectedFilter=event.detail |  | ||||||
|                   }} selectedValue={selectedFilter} placeholder="Filter by Organization/ Team" containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" items={selectgroups} isMulti={true}></Select> |  | ||||||
|       </div> |  | ||||||
|       <div |  | ||||||
|         class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> |  | ||||||
|         <table class="divide-y divide-gray-200 w-full"> |  | ||||||
|           <thead class="bg-gray-50"> |  | ||||||
|             <tr> |  | ||||||
|               <th |  | ||||||
|                 scope="col" |  | ||||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |  | ||||||
|                 {$_('name')} |  | ||||||
|               </th> |  | ||||||
|               <th |  | ||||||
|                 scope="col" |  | ||||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |  | ||||||
|                 {$_('contact-information')} |  | ||||||
|               </th> |  | ||||||
|               <th |  | ||||||
|                 scope="col" |  | ||||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |  | ||||||
|                 {$_('group')} |  | ||||||
|               </th> |  | ||||||
|               <th |  | ||||||
|                 scope="col" |  | ||||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |  | ||||||
|                 {$_('distance-in-km')} |  | ||||||
|               </th> |  | ||||||
|               <th scope="col" class="relative px-6 py-3"> |  | ||||||
|                 <span class="sr-only">{$_('action')}</span> |  | ||||||
|               </th> |  | ||||||
|             </tr> |  | ||||||
|           </thead> |  | ||||||
|           <tbody class="divide-y divide-gray-200"> |  | ||||||
|             {#each current_runners as runner} |  | ||||||
|               {#if runner.firstname.toLowerCase().includes(searchvalue.toLowerCase())||runner.middlename.toLowerCase().includes(searchvalue.toLowerCase())||runner.lastname.toLowerCase().includes(searchvalue.toLowerCase())||should_display_based_on_id(runner.id)} |  | ||||||
|                 {#if filterGroupIDs.includes(runner.group.id)||filterGroupIDs.includes(runner.group.parentGroup?.id)||filterGroupIDs.length===0} |  | ||||||
|                 <tr data-rowid="user_{runner.id}" data-groupid={runner.group.id}> |  | ||||||
|                   <td class="px-6 py-4 whitespace-nowrap"> |  | ||||||
|                     <div class="flex items-center"> |  | ||||||
|                       <div class="ml-4"> |  | ||||||
|                         <div class="text-sm font-medium text-gray-900"> |  | ||||||
|                           {runner.firstname} |  | ||||||
|                           {runner.middlename || ''} |  | ||||||
|                           {runner.lastname} |  | ||||||
|                         </div> |  | ||||||
|                       </div> |  | ||||||
|                     </div> |  | ||||||
|                   </td> |  | ||||||
|                   <td class="px-6 py-4 whitespace-nowrap"> |  | ||||||
|                     {#if runner.email} |  | ||||||
|                       <div class="text-sm text-gray-500">{runner.email}</div> |  | ||||||
|                     {/if} |  | ||||||
|                     {#if runner.phone} |  | ||||||
|                       <div class="text-sm text-gray-500">{runner.phone}</div> |  | ||||||
|                     {/if} |  | ||||||
|                   </td> |  | ||||||
|                   <td class="px-6 py-4 whitespace-nowrap"> |  | ||||||
|                     {#if runner.group.responseType === 'RUNNERTEAM'} |  | ||||||
|                       <a |  | ||||||
|                         href="../teams/{runner.group.id}" |  | ||||||
|                         class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a> |  | ||||||
|                     {/if} |  | ||||||
|                     {#if runner.group.responseType === 'RUNNERORGANIZATION'} |  | ||||||
|                       <a |  | ||||||
|                         href="../orgs/{runner.group.id}" |  | ||||||
|                         class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a> |  | ||||||
|                     {/if} |  | ||||||
|                   </td> |  | ||||||
|                   <td class="px-6 py-4 whitespace-nowrap">{runner.distance}</td> |  | ||||||
|                   {#if active_deletes[runner.id] === true} |  | ||||||
|                     <td |  | ||||||
|                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> |  | ||||||
|                       <button |  | ||||||
|                         on:click={() => { |  | ||||||
|                           active_deletes[runner.id] = false; |  | ||||||
|                         }} |  | ||||||
|                         tabindex="0" |  | ||||||
|                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel |  | ||||||
|                         Delete</button> |  | ||||||
|                       <button |  | ||||||
|                         on:click={() => { |  | ||||||
|                           RunnerService.runnerControllerRemove(runner.id, true) |  | ||||||
|                             .then((resp) => { |  | ||||||
|                               current_runners = current_runners.filter((obj) => obj.id !== runner.id); |  | ||||||
|                             }) |  | ||||||
|                             .catch((err) => { |  | ||||||
|                               // error deleting user |  | ||||||
|                             }); |  | ||||||
|                         }} |  | ||||||
|                         tabindex="0" |  | ||||||
|                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> |  | ||||||
|                     </td> |  | ||||||
|                   {:else} |  | ||||||
|                     <td |  | ||||||
|                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> |  | ||||||
|                       <a |  | ||||||
|                         href="./{runner.id}" |  | ||||||
|                         class="text-indigo-600 hover:text-indigo-900">Edit</a> |  | ||||||
|                       {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')} |  | ||||||
|                         <button |  | ||||||
|                           on:click={() => { |  | ||||||
|                             active_deletes[runner.id] = true; |  | ||||||
|                           }} |  | ||||||
|                           tabindex="0" |  | ||||||
|                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> |  | ||||||
|                       {/if} |  | ||||||
|                     </td> |  | ||||||
|                   {/if} |  | ||||||
|                 </tr> |  | ||||||
|                 {/if} |  | ||||||
|               {/if} |  | ||||||
|             {/each} |  | ||||||
|           </tbody> |  | ||||||
|         </table> |  | ||||||
|       </div> |  | ||||||
|     {/if} |  | ||||||
|   {:catch error} |  | ||||||
|     <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> |  | ||||||
|       <span class="inline-block align-middle mr-8"> |  | ||||||
|         <b class="capitalize">{$_('general_promise_error')}</b> |  | ||||||
|         {error} |  | ||||||
|       </span> |  | ||||||
|     </div> |  | ||||||
|   {/await} |  | ||||||
| {/if} |  | ||||||
| @@ -1,35 +0,0 @@ | |||||||
| <script> |  | ||||||
|   import { _ } from "svelte-i18n"; |  | ||||||
| import FormLayout from "./FormLayout.svelte"; |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12"> |  | ||||||
|   <div class="text-center mb-8"> |  | ||||||
|     <h1 |  | ||||||
|       class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"> |  | ||||||
|       🔨<br />{$_('settings')} |  | ||||||
|     </h1> |  | ||||||
|     <p |  | ||||||
|       class="mt-2 max-w-xl mx-auto text-xl lg:max-w-3xl lg:text-2xl text-gray-300"> |  | ||||||
|       <span class="text-lg">configure your profile however you want</span> |  | ||||||
|     </p> |  | ||||||
|   </div> |  | ||||||
| </div> |  | ||||||
|  |  | ||||||
| <div class="pt-0 pb-16 bg-gray-50 overflow-hidden lg:pt-12 lg:py-24"> |  | ||||||
|   <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> |  | ||||||
|     <!-- <h2 class="text-4xl font-display font-semibold text-gray-900 md:text-5xl"> |  | ||||||
|       General |  | ||||||
|     </h2> --> |  | ||||||
|     <div |  | ||||||
|       class="max-w-3xl mx-auto text-xl leading-8 font-medium text-gray-900 mb-16"> |  | ||||||
|       <p class="text-center"> |  | ||||||
|         Lorem ipsum dolor sit amet consectetur, adipisicing elit. Temporibus et |  | ||||||
|         amet voluptate nulla accusantium vero blanditiis nobis facere veritatis. |  | ||||||
|         Impedit deserunt saepe aliquid unde consequuntur officia consequatur |  | ||||||
|         fugit iusto dolorem? |  | ||||||
|       </p> |  | ||||||
|     </div> |  | ||||||
|     <FormLayout /> |  | ||||||
|   </div> |  | ||||||
| </div> |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| <script> |  | ||||||
|   import { _, locale } from "svelte-i18n"; |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <div>$locale $_('hallo')</div> |  | ||||||
| @@ -9,20 +9,15 @@ | |||||||
|   let usersEmail = ""; |   let usersEmail = ""; | ||||||
|   function reset() { |   function reset() { | ||||||
|     if (isEmail(usersEmail)) { |     if (isEmail(usersEmail)) { | ||||||
|       AuthService.authControllerGetResetToken({ email: usersEmail }) |       AuthService.authControllerGetResetToken("de", { email: usersEmail }) | ||||||
|         .then((resp) => { |         .then((resp) => { | ||||||
|           console.log(resp); |  | ||||||
|           console.log(resp.resetToken); |  | ||||||
|           Toastify({ |           Toastify({ | ||||||
|             text: $_("mail-validation-in-progress"), |             text: $_("mail-validation-in-progress"), | ||||||
|             duration: 3500, |             duration: 3500, | ||||||
|           }).showToast(); |           }).showToast(); | ||||||
|           reset_mail_sent = true; |           reset_mail_sent = true; | ||||||
|         }) |         }) | ||||||
|         .catch((err) => { |         .catch((err) => {}); | ||||||
|           console.log(err.body.name); |  | ||||||
|           console.log(err.body.message); |  | ||||||
|         }); |  | ||||||
|     } else { |     } else { | ||||||
|       Toastify({ |       Toastify({ | ||||||
|         text: $_("invalid-mail-reset"), |         text: $_("invalid-mail-reset"), | ||||||
| @@ -40,9 +35,7 @@ | |||||||
|         {$_('application_name')} |         {$_('application_name')} | ||||||
|       </p> |       </p> | ||||||
|       <p class="mt-2 mb-2 text-sm text-center text-gray-900"> |       <p class="mt-2 mb-2 text-sm text-center text-gray-900"> | ||||||
|         Passwort-Reset Mail wurde an |         {$_('password-reset-mail-sent', { values: { usersEmail: usersEmail } })} | ||||||
|         {usersEmail} |  | ||||||
|         geschickt |  | ||||||
|       </p> |       </p> | ||||||
|       <div class="mt-6"> |       <div class="mt-6"> | ||||||
|         <div class="mt-6"> |         <div class="mt-6"> | ||||||
| @@ -63,7 +56,7 @@ | |||||||
|         {$_('application_name')} |         {$_('application_name')} | ||||||
|       </p> |       </p> | ||||||
|       <p class="mt-6 text-sm text-center text-gray-900"> |       <p class="mt-6 text-sm text-center text-gray-900"> | ||||||
|         {$_('forgot_password?')} |         {$_('forgot_password')} | ||||||
|       </p> |       </p> | ||||||
|       <p class="mt-2 mb-2 text-sm text-center text-gray-900"> |       <p class="mt-2 mb-2 text-sm text-center text-gray-900"> | ||||||
|         {$_('dont-panic-were-resetting-it')} |         {$_('dont-panic-were-resetting-it')} | ||||||
| @@ -1,10 +1,10 @@ | |||||||
| <script> | <script> | ||||||
|   import store from "../store.js"; |   import store from "../../store.js"; | ||||||
|   import localForage from "localforage"; |   import localForage from "localforage"; | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   store.init(); |   store.init(); | ||||||
|   import { OpenAPI, AuthService } from "@odit/lfk-client-js"; |   import { OpenAPI, AuthService } from "@odit/lfk-client-js"; | ||||||
|   import Footer from "./Footer.svelte"; |   import Footer from "../general/Footer.svelte"; | ||||||
|   import Toastify from "toastify-js"; |   import Toastify from "toastify-js"; | ||||||
|   // ------ |   // ------ | ||||||
|   let username = "demo"; |   let username = "demo"; | ||||||
| @@ -136,7 +136,7 @@ | |||||||
|       <a |       <a | ||||||
|         href="/forgot_password" |         href="/forgot_password" | ||||||
|         class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"> |         class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"> | ||||||
|         {$_('forgot_password?')} |         {$_('forgot_password')} | ||||||
|       </a> |       </a> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| @@ -7,12 +7,12 @@ | |||||||
|     <div class="w-full bg-white flex items-center justify-center "> |     <div class="w-full bg-white flex items-center justify-center "> | ||||||
|       <div class="max-w-sm m-8"> |       <div class="max-w-sm m-8"> | ||||||
|         <div class="text-black text-5xl md:text-15xl font-black"> |         <div class="text-black text-5xl md:text-15xl font-black"> | ||||||
|           Internal Error |           {$_('internal-error')} | ||||||
|         </div> |         </div> | ||||||
|         <div class="w-16 h-1 bg-purple-light my-3 md:my-6" /> |         <div class="w-16 h-1 bg-purple-light my-3 md:my-6" /> | ||||||
|         <p |         <p | ||||||
|           class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal"> |           class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal"> | ||||||
|           Something went wrong in the UI logic |           {$_('generic-ui-logic-error')} | ||||||
|         </p> |         </p> | ||||||
|         <a |         <a | ||||||
|           href="/" |           href="/" | ||||||
							
								
								
									
										436
									
								
								src/components/contacts/AddContactModal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,436 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { clickOutside } from "../base/outsideclick"; | ||||||
|  |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|  |   import { | ||||||
|  |     GroupContactService, | ||||||
|  |     RunnerTeamService, | ||||||
|  |     RunnerOrganizationService, | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import isEmail from "validator/es/lib/isEmail"; | ||||||
|  |   import isMobilePhone from "validator/es/lib/isMobilePhone"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   export let modal_open; | ||||||
|  |   export let current_contacts; | ||||||
|  |   $: selected_team = []; | ||||||
|  |   let firstname_input; | ||||||
|  |   let lastname_input; | ||||||
|  |   let middlename_input; | ||||||
|  |   let phone_input; | ||||||
|  |   let email_input; | ||||||
|  |   let address_input1; | ||||||
|  |   let address_input2; | ||||||
|  |   let address_zipcode; | ||||||
|  |   let address_city; | ||||||
|  |   let teams = []; | ||||||
|  |   let orgs = []; | ||||||
|  |   RunnerTeamService.runnerTeamControllerGetAll().then((val) => { | ||||||
|  |     teams = val; | ||||||
|  |   }); | ||||||
|  |   RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { | ||||||
|  |     orgs = val; | ||||||
|  |   }); | ||||||
|  |   function focus(el) { | ||||||
|  |     el.focus(); | ||||||
|  |   } | ||||||
|  |   $: middlename_input_value = ""; | ||||||
|  |   $: phone_input_value = ""; | ||||||
|  |   $: email_input_value = ""; | ||||||
|  |   $: lastname_input_value = ""; | ||||||
|  |   $: firstname_input_value = ""; | ||||||
|  |   $: address_input1_value = ""; | ||||||
|  |   $: address_input2_value = ""; | ||||||
|  |   $: address_zipcode_value = ""; | ||||||
|  |   $: address_city_value = ""; | ||||||
|  |   $: processed_last_submit = true; | ||||||
|  |   $: address_checked = true; | ||||||
|  |   $: isPhoneValidOrEmpty = | ||||||
|  |     (phone_input_value.includes("+") && | ||||||
|  |       isMobilePhone( | ||||||
|  |         phone_input_value | ||||||
|  |           .replaceAll("(", "") | ||||||
|  |           .replaceAll(")", "") | ||||||
|  |           .replaceAll("-", "") | ||||||
|  |           .replaceAll(" ", "") | ||||||
|  |       )) || | ||||||
|  |     phone_input_value === ""; | ||||||
|  |   $: isEmailValidOrEmpty = | ||||||
|  |     isEmail(email_input_value) || email_input_value === ""; | ||||||
|  |   $: isLastnameValid = lastname_input_value.trim().length !== 0; | ||||||
|  |   $: isFirstnameValid = firstname_input_value.trim().length !== 0; | ||||||
|  |   $: isAddress1Valid = address_input1_value.trim().length !== 0; | ||||||
|  |   $: iszipcodevalid = address_zipcode_value.trim().length !== 0; | ||||||
|  |   $: iscityvalid = address_city_value.trim().length !== 0; | ||||||
|  |   $: createbtnenabled = | ||||||
|  |     isFirstnameValid && | ||||||
|  |     isLastnameValid && | ||||||
|  |     isEmailValidOrEmpty && | ||||||
|  |     isPhoneValidOrEmpty && | ||||||
|  |     ((isAddress1Valid && iszipcodevalid && iscityvalid) || | ||||||
|  |       address_checked === false); | ||||||
|  |   (() => { | ||||||
|  |     document.onkeydown = (e) => { | ||||||
|  |       e = e || window.event; | ||||||
|  |       if (e.key === "Escape") { | ||||||
|  |         modal_open = false; | ||||||
|  |       } | ||||||
|  |       if (e.keyCode === 13) { | ||||||
|  |         if (createbtnenabled === true) { | ||||||
|  |           createbtnenabled = false; | ||||||
|  |           submit(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   })(); | ||||||
|  |   function submit() { | ||||||
|  |     if (processed_last_submit === true) { | ||||||
|  |       processed_last_submit = false; | ||||||
|  |       const toast = Toastify({ | ||||||
|  |         text: "Contact is being added...", | ||||||
|  |         duration: -1, | ||||||
|  |       }).showToast(); | ||||||
|  |       let address = {}; | ||||||
|  |       if (address_checked === true) { | ||||||
|  |         address = { | ||||||
|  |           address1: address_input1_value, | ||||||
|  |           address2: address_input2_value || "", | ||||||
|  |           postalcode: address_zipcode_value, | ||||||
|  |           city: address_city_value, | ||||||
|  |           country: "DE", | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |       let postdata = { | ||||||
|  |         groups: selected_team, | ||||||
|  |         firstname: firstname_input_value, | ||||||
|  |         lastname: lastname_input_value, | ||||||
|  |         address, | ||||||
|  |       }; | ||||||
|  |       if (middlename_input_value) { | ||||||
|  |         postdata.middlename = middlename_input_value; | ||||||
|  |       } | ||||||
|  |       if (phone_input_value) { | ||||||
|  |         postdata.phone = phone_input_value; | ||||||
|  |       } | ||||||
|  |       if (email_input_value) { | ||||||
|  |         postdata.email = email_input_value; | ||||||
|  |       } | ||||||
|  |       GroupContactService.groupContactControllerPost(postdata) | ||||||
|  |         .then((result) => { | ||||||
|  |           firstname_input_value = ""; | ||||||
|  |           lastname_input_value = ""; | ||||||
|  |           middlename_input_value = ""; | ||||||
|  |           email_input_value = ""; | ||||||
|  |           modal_open = false; | ||||||
|  |           // | ||||||
|  |           Toastify({ | ||||||
|  |             text: "Contact added", | ||||||
|  |             duration: 500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |           current_contacts.push(result); | ||||||
|  |           current_contacts = current_contacts; | ||||||
|  |         }) | ||||||
|  |         .catch((err) => { | ||||||
|  |           // | ||||||
|  |         }) | ||||||
|  |         .finally(() => { | ||||||
|  |           processed_last_submit = true; | ||||||
|  |           // | ||||||
|  |           toast.hideToast(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if modal_open} | ||||||
|  |   <div | ||||||
|  |     class="fixed z-10 inset-0 overflow-y-auto" | ||||||
|  |     use:focusTrap | ||||||
|  |     use:clickOutside | ||||||
|  |     on:click_outside={() => { | ||||||
|  |       modal_open = false; | ||||||
|  |     }}> | ||||||
|  |     <div | ||||||
|  |       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||||
|  |       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||||
|  |         <div | ||||||
|  |           class="absolute inset-0 bg-gray-500 opacity-75" | ||||||
|  |           data-id="modal_backdrop" /> | ||||||
|  |       </div> | ||||||
|  |       <span | ||||||
|  |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|  |         aria-hidden="true">​</span> | ||||||
|  |       <div | ||||||
|  |         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|  |         role="dialog" | ||||||
|  |         aria-modal="true" | ||||||
|  |         aria-labelledby="modal-headline"> | ||||||
|  |         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||||
|  |           <div class="sm:flex sm:items-start"> | ||||||
|  |             <div | ||||||
|  |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|  |               <svg | ||||||
|  |                 class="h-6 w-6 text-blue-600" | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|  |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |                 {$_('create-a-new-contact')} | ||||||
|  |               </h3> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <p class="text-sm text-gray-500"> | ||||||
|  |                   {$_('please-provide-the-required-information-to-add-a-new-contact')} | ||||||
|  |                 </p> | ||||||
|  |               </div> | ||||||
|  |               <div class="grid grid-cols-6 gap-6"> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="firstname" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('first-name')}</label> | ||||||
|  |                   <input | ||||||
|  |                     use:focus | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('first-name')} | ||||||
|  |                     class:border-red-500={!isFirstnameValid} | ||||||
|  |                     class:focus:border-red-500={!isFirstnameValid} | ||||||
|  |                     class:focus:ring-red-500={!isFirstnameValid} | ||||||
|  |                     bind:value={firstname_input_value} | ||||||
|  |                     bind:this={firstname_input} | ||||||
|  |                     type="text" | ||||||
|  |                     name="firstname" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !isFirstnameValid} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('first-name-is-required')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="trackname" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('middle-name')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('middle-name')} | ||||||
|  |                     bind:value={middlename_input_value} | ||||||
|  |                     bind:this={middlename_input} | ||||||
|  |                     type="text" | ||||||
|  |                     name="trackname" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="lastname" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('last-name')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder="{$_('last-name')}" | ||||||
|  |                     class:border-red-500={!isLastnameValid} | ||||||
|  |                     class:focus:border-red-500={!isLastnameValid} | ||||||
|  |                     class:focus:ring-red-500={!isLastnameValid} | ||||||
|  |                     bind:value={lastname_input_value} | ||||||
|  |                     bind:this={lastname_input} | ||||||
|  |                     type="text" | ||||||
|  |                     name="lastname" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !isLastnameValid} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('last-name-is-required')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="team" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('teams')}</label> | ||||||
|  |                   <select | ||||||
|  |                     name="team" | ||||||
|  |                     multiple | ||||||
|  |                     bind:value={selected_team} | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"> | ||||||
|  |                     {#each teams as team} | ||||||
|  |                       <option value={team.id}> | ||||||
|  |                         {team.parentGroup.name} | ||||||
|  |                         > | ||||||
|  |                         {team.name} | ||||||
|  |                       </option> | ||||||
|  |                     {/each} | ||||||
|  |                     {#each orgs as org} | ||||||
|  |                       <option value={org.id}>{org.name}</option> | ||||||
|  |                     {/each} | ||||||
|  |                   </select> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="phone" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('phone')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('phone')} | ||||||
|  |                     class:border-red-500={!isPhoneValidOrEmpty} | ||||||
|  |                     class:focus:border-red-500={!isPhoneValidOrEmpty} | ||||||
|  |                     class:focus:ring-red-500={!isPhoneValidOrEmpty} | ||||||
|  |                     bind:value={phone_input_value} | ||||||
|  |                     bind:this={phone_input} | ||||||
|  |                     type="tel" | ||||||
|  |                     name="phone" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !isPhoneValidOrEmpty} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="email" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('e-mail-adress')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('e-mail-adress')} | ||||||
|  |                     class:border-red-500={!isEmailValidOrEmpty} | ||||||
|  |                     class:focus:border-red-500={!isEmailValidOrEmpty} | ||||||
|  |                     class:focus:ring-red-500={!isEmailValidOrEmpty} | ||||||
|  |                     bind:value={email_input_value} | ||||||
|  |                     bind:this={email_input} | ||||||
|  |                     type="email" | ||||||
|  |                     name="email" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !isEmailValidOrEmpty} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('valid-email-is-required')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="flex items-start"> | ||||||
|  |                   <div class="flex items-center h-5"> | ||||||
|  |                     <input | ||||||
|  |                       bind:checked={address_checked} | ||||||
|  |                       id="comments" | ||||||
|  |                       name="comments" | ||||||
|  |                       type="checkbox" | ||||||
|  |                       class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |                   </div> | ||||||
|  |                   <div class="ml-3 text-sm"> | ||||||
|  |                     <label | ||||||
|  |                       for="comments" | ||||||
|  |                       class="font-medium text-gray-700">{$_('address')}</label> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |                 {#if address_checked === true} | ||||||
|  |                   <div class="col-span-6"> | ||||||
|  |                     <label | ||||||
|  |                       for="address1" | ||||||
|  |                       class="block text-sm font-medium text-gray-700">{$_('address')}</label> | ||||||
|  |                     <input | ||||||
|  |                       autocomplete="off" | ||||||
|  |                       placeholder="{$_('address')}" | ||||||
|  |                       class:border-red-500={!isAddress1Valid} | ||||||
|  |                       class:focus:border-red-500={!isAddress1Valid} | ||||||
|  |                       class:focus:ring-red-500={!isAddress1Valid} | ||||||
|  |                       bind:value={address_input1_value} | ||||||
|  |                       bind:this={address_input1} | ||||||
|  |                       type="text" | ||||||
|  |                       name="address1" | ||||||
|  |                       class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                     {#if !isAddress1Valid} | ||||||
|  |                       <span | ||||||
|  |                         class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                         {$_('address-is-required')} | ||||||
|  |                       </span> | ||||||
|  |                     {/if} | ||||||
|  |                   </div> | ||||||
|  |                   <div class="col-span-6"> | ||||||
|  |                     <label | ||||||
|  |                       for="address2" | ||||||
|  |                       class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label> | ||||||
|  |                     <input | ||||||
|  |                       autocomplete="off" | ||||||
|  |                       placeholder={$_('apartment-suite-etc')} | ||||||
|  |                       bind:value={address_input2_value} | ||||||
|  |                       bind:this={address_input2} | ||||||
|  |                       type="text" | ||||||
|  |                       name="address2" | ||||||
|  |                       class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   </div> | ||||||
|  |                   <div class="col-span-6"> | ||||||
|  |                     <label | ||||||
|  |                       for="zipcode" | ||||||
|  |                       class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label> | ||||||
|  |                     <input | ||||||
|  |                       autocomplete="off" | ||||||
|  |                       placeholder={$_('zip-postal-code')} | ||||||
|  |                       class:border-red-500={!iszipcodevalid} | ||||||
|  |                       class:focus:border-red-500={!iszipcodevalid} | ||||||
|  |                       class:focus:ring-red-500={!iszipcodevalid} | ||||||
|  |                       bind:value={address_zipcode_value} | ||||||
|  |                       bind:this={address_zipcode} | ||||||
|  |                       type="text" | ||||||
|  |                       name="zipcode" | ||||||
|  |                       class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                     {#if !iszipcodevalid} | ||||||
|  |                       <span | ||||||
|  |                         class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                         {$_('valid-zipcode-postal-code-is-required')} | ||||||
|  |                       </span> | ||||||
|  |                     {/if} | ||||||
|  |                   </div> | ||||||
|  |                   <div class="col-span-6"> | ||||||
|  |                     <label | ||||||
|  |                       for="city" | ||||||
|  |                       class="block text-sm font-medium text-gray-700">{$_('city')}</label> | ||||||
|  |                     <input | ||||||
|  |                       autocomplete="off" | ||||||
|  |                       placeholder="{$_('city')}" | ||||||
|  |                       class:border-red-500={!iscityvalid} | ||||||
|  |                       class:focus:border-red-500={!iscityvalid} | ||||||
|  |                       class:focus:ring-red-500={!iscityvalid} | ||||||
|  |                       bind:value={address_city_value} | ||||||
|  |                       bind:this={address_city} | ||||||
|  |                       type="text" | ||||||
|  |                       name="city" | ||||||
|  |                       class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                     {#if !iscityvalid} | ||||||
|  |                       <span | ||||||
|  |                         class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                         {$_('valid-city-is-required')} | ||||||
|  |                       </span> | ||||||
|  |                     {/if} | ||||||
|  |                   </div> | ||||||
|  |                 {/if} | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||||
|  |           <button | ||||||
|  |             disabled={!createbtnenabled} | ||||||
|  |             class:opacity-50={!createbtnenabled} | ||||||
|  |             on:click={submit} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('create')} | ||||||
|  |           </button> | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |               modal_open = false; | ||||||
|  |             }} | ||||||
|  |             type="button" | ||||||
|  |             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('cancel')} | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
							
								
								
									
										394
									
								
								src/components/contacts/ContactDetail.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,394 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import { | ||||||
|  |     GroupContactService, | ||||||
|  |     RunnerTeamService, | ||||||
|  |     RunnerOrganizationService, | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import PromiseError from "../base/PromiseError.svelte"; | ||||||
|  |   import isEmail from "validator/es/lib/isEmail"; | ||||||
|  |   let data_loaded = false; | ||||||
|  |   let orgs = []; | ||||||
|  |   let teams = []; | ||||||
|  |   export let params; | ||||||
|  |   $: delete_triggered = false; | ||||||
|  |   $: original_data = {}; | ||||||
|  |   $: editable = {}; | ||||||
|  |   $: changes_performed = !( | ||||||
|  |     JSON.stringify(original_data) === JSON.stringify(editable) | ||||||
|  |   ); | ||||||
|  |   $: isEmailValid = | ||||||
|  |     (editable.email || "") === "" || | ||||||
|  |     (editable.email && isEmail(editable.email || "")); | ||||||
|  |   $: isFirstnameValid = editable.firstname !== ""; | ||||||
|  |   $: isLastnameValid = editable.lastname !== ""; | ||||||
|  |   $: save_enabled = | ||||||
|  |     changes_performed && | ||||||
|  |     isFirstnameValid && | ||||||
|  |     isLastnameValid && | ||||||
|  |     isEmailValid && | ||||||
|  |     isPhoneValidOrEmpty && | ||||||
|  |     ((isAddress1Valid && iszipcodevalid && iscityvalid) || | ||||||
|  |     editable.address_checked === false); | ||||||
|  |   const promise = GroupContactService.groupContactControllerGetOne( | ||||||
|  |     params.contact | ||||||
|  |   ).then((data) => { | ||||||
|  |     data_loaded = true; | ||||||
|  |     original_data = Object.assign(original_data, data); | ||||||
|  |     editable = Object.assign(editable, original_data); | ||||||
|  |     editable.groups = editable.groups.map((g) => g.id); | ||||||
|  |     original_data.groups = original_data.groups.map((g) => g.id); | ||||||
|  |     editable.address_checked = editable.address.address1 !== null; | ||||||
|  |     original_data.address_checked = editable.address.address1 !== null; | ||||||
|  |     if(editable.address_checked===false){ | ||||||
|  |       editable.address = { | ||||||
|  |         address1: "", | ||||||
|  |         address2: "", | ||||||
|  |         city: "", | ||||||
|  |         postalcode: "", | ||||||
|  |         country: "" | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { | ||||||
|  |     orgs = val; | ||||||
|  |   }); | ||||||
|  |   RunnerTeamService.runnerTeamControllerGetAll().then((val) => { | ||||||
|  |     teams = val; | ||||||
|  |   }); | ||||||
|  |   $: isPhoneValidOrEmpty = | ||||||
|  |     editable.phone?.includes("+") || | ||||||
|  |     editable.phone === "" || | ||||||
|  |     editable.phone === null; | ||||||
|  |   $: isAddress1Valid = editable.address?.address1?.trim().length !== 0; | ||||||
|  |   $: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0; | ||||||
|  |   $: iscityvalid = editable.address?.city?.trim().length !== 0; | ||||||
|  |   function submit() { | ||||||
|  |     if (data_loaded === true && save_enabled) { | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_("contact-is-being-updated"), | ||||||
|  |         duration: 2500, | ||||||
|  |       }).showToast(); | ||||||
|  |       editable.address.country = "DE"; | ||||||
|  |       if (editable.address_checked === false) { | ||||||
|  |         editable.address = null; | ||||||
|  |       } | ||||||
|  |       if (editable.email) editable.email = editable.email; | ||||||
|  |       if (editable.phone) editable.phone = editable.phone; | ||||||
|  |       if (editable.middlename) editable.middlename = editable.middlename; | ||||||
|  |       GroupContactService.groupContactControllerPut(original_data.id, editable) | ||||||
|  |         .then((resp) => { | ||||||
|  |           Object.assign(original_data, editable); | ||||||
|  |           original_data=original_data; | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_("updated-contact"), | ||||||
|  |             duration: 2500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |         }) | ||||||
|  |         .catch((err) => {}); | ||||||
|  |     } else { | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   function deleteContact() { | ||||||
|  |     GroupContactService.groupContactControllerRemove(original_data.id, true) | ||||||
|  |       .then((resp) => { | ||||||
|  |         location.replace("./"); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => {}); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#await promise} | ||||||
|  |   {$_('loading-contact-details')} | ||||||
|  | {:then} | ||||||
|  |   <section class="container p-5 select-none"> | ||||||
|  |     <div class="flex flex-row mb-4"> | ||||||
|  |       <div class="w-full"> | ||||||
|  |         <nav class="w-full flex"> | ||||||
|  |           <ol class="list-none flex flex-row items-center justify-start"> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <svg | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center ml-2"> | ||||||
|  |               <a class="mr-2" href="./">{$_('contacts')}</a><svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 mr-2 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><line | ||||||
|  |                   x1="5" | ||||||
|  |                   y1="12" | ||||||
|  |                   x2="19" | ||||||
|  |                   y2="12" /> | ||||||
|  |                 <polyline points="12 5 19 12 12 19" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <span class="mr-2">{original_data.firstname} | ||||||
|  |                 {original_data.middlename || ''} | ||||||
|  |                 {original_data.lastname}</span> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </nav> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="mb-8 text-3xl font-extrabold leading-tight"> | ||||||
|  |       {original_data.firstname} | ||||||
|  |       {original_data.middlename || ''} | ||||||
|  |       {original_data.lastname} | ||||||
|  |       <span data-id="contact_actions_${editable.id}"> | ||||||
|  |         {#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:DELETE')} | ||||||
|  |           {#if delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={deleteContact} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button> | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = !delete_triggered; | ||||||
|  |               }} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> | ||||||
|  |           {/if} | ||||||
|  |           {#if !delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = true; | ||||||
|  |               }} | ||||||
|  |               type="button" | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-contact')}</button> | ||||||
|  |           {/if} | ||||||
|  |         {/if} | ||||||
|  |         {#if !delete_triggered} | ||||||
|  |           <button | ||||||
|  |             disabled={!save_enabled} | ||||||
|  |             class:opacity-50={!save_enabled} | ||||||
|  |             type="button" | ||||||
|  |             on:click={submit} | ||||||
|  |             class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> | ||||||
|  |         {/if} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |     <!--  --> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label | ||||||
|  |         for="firstname" | ||||||
|  |         class="font-medium text-gray-700">{$_('first-name')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('first-name')} | ||||||
|  |         type="text" | ||||||
|  |         class:border-red-500={!isFirstnameValid} | ||||||
|  |         class:focus:border-red-500={!isFirstnameValid} | ||||||
|  |         class:focus:ring-red-500={!isFirstnameValid} | ||||||
|  |         bind:value={editable.firstname} | ||||||
|  |         name="firstname" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       {#if !isFirstnameValid} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('first-name-is-required')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label | ||||||
|  |         for="middlename" | ||||||
|  |         class="font-medium text-gray-700">{$_('middle-name')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('middle-name')} | ||||||
|  |         type="text" | ||||||
|  |         bind:value={editable.middlename} | ||||||
|  |         name="middlename" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label | ||||||
|  |         for="lastname" | ||||||
|  |         class="font-medium text-gray-700">{$_('last-name')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('last-name')} | ||||||
|  |         type="text" | ||||||
|  |         bind:value={editable.lastname} | ||||||
|  |         class:border-red-500={!isLastnameValid} | ||||||
|  |         class:focus:border-red-500={!isLastnameValid} | ||||||
|  |         class:focus:ring-red-500={!isLastnameValid} | ||||||
|  |         name="lastname" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       {#if !isLastnameValid} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('last-name-is-required')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label | ||||||
|  |         for="email" | ||||||
|  |         class="font-medium text-gray-700">{$_('e-mail-adress')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('e-mail-adress')} | ||||||
|  |         type="email" | ||||||
|  |         bind:value={editable.email} | ||||||
|  |         class:border-red-500={!isEmailValid} | ||||||
|  |         class:focus:border-red-500={!isEmailValid} | ||||||
|  |         class:focus:ring-red-500={!isEmailValid} | ||||||
|  |         name="email" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       {#if !isEmailValid} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('valid-email-is-required')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label for="phone" class="font-medium text-gray-700">{$_('phone')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('phone')} | ||||||
|  |         type="tel" | ||||||
|  |         class:border-red-500={!isPhoneValidOrEmpty} | ||||||
|  |         class:focus:border-red-500={!isPhoneValidOrEmpty} | ||||||
|  |         class:focus:ring-red-500={!isPhoneValidOrEmpty} | ||||||
|  |         bind:value={editable.phone} | ||||||
|  |         name="phone" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       {#if !isPhoneValidOrEmpty} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('valid-international-phone-number-is-required')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <span class="font-medium text-gray-700">{$_('groups')}</span> | ||||||
|  |       <select | ||||||
|  |         bind:value={editable.groups} | ||||||
|  |         name="team" | ||||||
|  |         multiple | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"> | ||||||
|  |         {#each teams as team} | ||||||
|  |           <option value={team.id}> | ||||||
|  |             {team.parentGroup.name} | ||||||
|  |             > | ||||||
|  |             {team.name} | ||||||
|  |           </option> | ||||||
|  |         {/each} | ||||||
|  |         {#each orgs as org} | ||||||
|  |           <option value={org.id}>{org.name}</option> | ||||||
|  |         {/each} | ||||||
|  |       </select> | ||||||
|  |     </div> | ||||||
|  |     <!--  --> | ||||||
|  |     <div class="flex items-start mt-2"> | ||||||
|  |       <div class="flex items-center h-5"> | ||||||
|  |         <input | ||||||
|  |           bind:checked={editable.address_checked} | ||||||
|  |           id="comments" | ||||||
|  |           name="comments" | ||||||
|  |           type="checkbox" | ||||||
|  |           class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |       </div> | ||||||
|  |       <div class="ml-3 text-sm"> | ||||||
|  |         <label | ||||||
|  |           for="comments" | ||||||
|  |           class="font-medium text-gray-700">{$_('address')}</label> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     {#if editable.address_checked === true} | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="address1" | ||||||
|  |           class="block text-sm font-medium text-gray-700">{$_('address')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder="Address" | ||||||
|  |           class:border-red-500={!isAddress1Valid} | ||||||
|  |           class:focus:border-red-500={!isAddress1Valid} | ||||||
|  |           class:focus:ring-red-500={!isAddress1Valid} | ||||||
|  |           bind:value={editable.address.address1} | ||||||
|  |           type="text" | ||||||
|  |           name="address1" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |         {#if !isAddress1Valid} | ||||||
|  |           <span | ||||||
|  |             class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |             {$_('address-is-required')} | ||||||
|  |           </span> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="address2" | ||||||
|  |           class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder={$_('apartment-suite-etc')} | ||||||
|  |           bind:value={editable.address.address2} | ||||||
|  |           type="text" | ||||||
|  |           name="address2" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       </div> | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="zipcode" | ||||||
|  |           class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder={$_('zip-postal-code')} | ||||||
|  |           class:border-red-500={!iszipcodevalid} | ||||||
|  |           class:focus:border-red-500={!iszipcodevalid} | ||||||
|  |           class:focus:ring-red-500={!iszipcodevalid} | ||||||
|  |           bind:value={editable.address.postalcode} | ||||||
|  |           type="text" | ||||||
|  |           name="zipcode" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |         {#if !iszipcodevalid} | ||||||
|  |           <span | ||||||
|  |             class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |             {$_('valid-zipcode-postal-code-is-required')} | ||||||
|  |           </span> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="city" | ||||||
|  |           class="block text-sm font-medium text-gray-700">{$_('city')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder={$_('city')} | ||||||
|  |           class:border-red-500={!iscityvalid} | ||||||
|  |           class:focus:border-red-500={!iscityvalid} | ||||||
|  |           class:focus:ring-red-500={!iscityvalid} | ||||||
|  |           bind:value={editable.address.city} | ||||||
|  |           type="text" | ||||||
|  |           name="city" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |         {#if !iscityvalid} | ||||||
|  |           <span | ||||||
|  |             class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |             {$_('valid-city-is-required')} | ||||||
|  |           </span> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |   </section> | ||||||
|  | {:catch error} | ||||||
|  |   <PromiseError {error} /> | ||||||
|  | {/await} | ||||||
							
								
								
									
										29
									
								
								src/components/contacts/Contacts.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import AddContactModal from "./AddContactModal.svelte"; | ||||||
|  |   import ContactsOverview from "./ContactsOverview.svelte"; | ||||||
|  |   export let modal_open = false; | ||||||
|  |   let current_contacts = []; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <section class="container p-5"> | ||||||
|  |   <span class="mb-1 text-3xl font-extrabold leading-tight"> | ||||||
|  |     {$_('contacts')} | ||||||
|  |     {#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:CREATE')} | ||||||
|  |       <button | ||||||
|  |         on:click={() => { | ||||||
|  |           modal_open = true; | ||||||
|  |         }} | ||||||
|  |         type="button" | ||||||
|  |         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |         {$_('create-a-new-contact')} | ||||||
|  |       </button> | ||||||
|  |     {/if} | ||||||
|  |   </span> | ||||||
|  |   <ContactsOverview bind:current_contacts /> | ||||||
|  | </section> | ||||||
|  |  | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:CREATE')} | ||||||
|  |   <AddContactModal bind:current_contacts bind:modal_open /> | ||||||
|  | {/if} | ||||||
							
								
								
									
										17
									
								
								src/components/contacts/ContactsEmptyState.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,17 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import AddContactModal from "./AddContactModal.svelte"; | ||||||
|  |   import team_empty from "../teams/team_empty.svg"; | ||||||
|  |   let modal_open = false; | ||||||
|  |   let current_contacts = []; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div class="text-center items-center justify-center"> | ||||||
|  |   <p class="mb-16 text-lg text-gray-500"> | ||||||
|  |     <img class="w-full h-44" src={team_empty} alt="" /> | ||||||
|  |     <span class="font-bold">{$_('there-are-no-contacts-added-yet')}</span><br /> | ||||||
|  |     <span>{$_('add-your-first-contact')}</span> | ||||||
|  |   </p> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | <AddContactModal bind:modal_open bind:current_contacts /> | ||||||
| @@ -1,41 +1,30 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   let modal_open = false; |  | ||||||
|   let delete_org = {}; |  | ||||||
|   import { RunnerOrganizationService } from "@odit/lfk-client-js"; |  | ||||||
|   import store from "../store"; |  | ||||||
|   import OrgsEmptyState from "./OrgsEmptyState.svelte"; |  | ||||||
|   import Toastify from "toastify-js"; |   import Toastify from "toastify-js"; | ||||||
|   import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte"; |   import { GroupContactService } from "@odit/lfk-client-js"; | ||||||
|   $: searchvalue = ""; |   const promise = GroupContactService.groupContactControllerGetAll().then( | ||||||
|   $: active_deletes = []; |     (result) => { | ||||||
|   export let current_organizations = []; |       current_contacts = result; | ||||||
| 
 |  | ||||||
|   const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then( |  | ||||||
|     (val) => { |  | ||||||
|       current_organizations = val; |  | ||||||
|     } |     } | ||||||
|   ); |   ); | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import ContactsEmptyState from "./ContactsEmptyState.svelte"; | ||||||
|  |   $: searchvalue = ""; | ||||||
|  |   $: active_deletes = []; | ||||||
|  |   export let current_contacts = []; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <ConfirmOrgDeletion | {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')} | ||||||
|   on:cancelDelete={(event) => { |  | ||||||
|     modal_open = false; |  | ||||||
|     active_deletes[event.detail.id] = false; |  | ||||||
|   }} |  | ||||||
|   bind:modal_open |  | ||||||
|   bind:delete_org /> |  | ||||||
| {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')} |  | ||||||
|   {#await promise} |   {#await promise} | ||||||
|     <div |     <div | ||||||
|       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" |       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||||
|       role="alert"> |       role="alert"> | ||||||
|       <p class="font-bold">organizations are being loaded...</p> |       <p class="font-bold">{$_('contacts-are-being-loaded')}</p> | ||||||
|       <p class="text-sm">{$_('this-might-take-a-moment')}</p> |       <p class="text-sm">{$_('this-might-take-a-moment')}</p> | ||||||
|     </div> |     </div> | ||||||
|   {:then} |   {:then} | ||||||
|     {#if current_organizations.length === 0} |     {#if current_contacts.length === 0} | ||||||
|       <OrgsEmptyState /> |       <ContactsEmptyState /> | ||||||
|     {:else} |     {:else} | ||||||
|       <input |       <input | ||||||
|         type="search" |         type="search" | ||||||
| @@ -51,36 +40,37 @@ | |||||||
|               <th |               <th | ||||||
|                 scope="col" |                 scope="col" | ||||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|                 Name |                 {$_('name')} | ||||||
|               </th> |               </th> | ||||||
|               <th |               <th | ||||||
|                 scope="col" |                 scope="col" | ||||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|                 Address |                 {$_('groups')} | ||||||
|               </th> |               </th> | ||||||
|               <th |               <th | ||||||
|                 scope="col" |                 scope="col" | ||||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|                 Contact |                 {$_('address')} | ||||||
|               </th> |               </th> | ||||||
|               <th scope="col" class="relative px-6 py-3"> |               <th scope="col" class="relative px-6 py-3"> | ||||||
|                 <span class="sr-only">Action</span> |                 <span class="sr-only">{$_('action')}</span> | ||||||
|               </th> |               </th> | ||||||
|             </tr> |             </tr> | ||||||
|           </thead> |           </thead> | ||||||
|           <tbody class="divide-y divide-gray-200"> |           <tbody class="divide-y divide-gray-200"> | ||||||
|             {#each current_organizations as o} |             {#each current_contacts as t} | ||||||
|               {#if Object.values(o) |               {#if Object.values(t) | ||||||
|                 .toString() |                 .toString() | ||||||
|                 .toLowerCase() |                 .toLowerCase() | ||||||
|                 .includes(searchvalue)} |                 .includes(searchvalue)} | ||||||
|                 <tr data-rowid="org_{o.id}"> |                 <tr data-rowid="team_{t.id}"> | ||||||
|                   <td class="px-6 py-4 whitespace-nowrap"> |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|                     <div class="flex items-center"> |                     <div class="flex items-center"> | ||||||
|                       <div class="ml-4"> |                       <div class="ml-4"> | ||||||
|                         <div |                         <div class="text-sm font-medium text-gray-900"> | ||||||
|                           class="text-sm font-medium text-gray-900"> |                           {t.firstname} | ||||||
|                           {o.name} |                           {t.middlename || ''} | ||||||
|  |                           {t.lastname} | ||||||
|                         </div> |                         </div> | ||||||
|                       </div> |                       </div> | ||||||
|                     </div> |                     </div> | ||||||
| @@ -88,11 +78,24 @@ | |||||||
|                   <td class="px-6 py-4 whitespace-nowrap"> |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|                     <div class="flex items-center"> |                     <div class="flex items-center"> | ||||||
|                       <div class="ml-4"> |                       <div class="ml-4"> | ||||||
|                         <div |                         <div class="text-sm font-medium text-gray-900"> | ||||||
|                           class="text-sm font-medium text-gray-900"> |                           {#if t.groups.length > 0} | ||||||
|                           {#if o.address} |                             {#each t.groups as g} | ||||||
|                             {JSON.stringify(o.address)} |                               {#if g.responseType === 'RUNNERORGANIZATION'} | ||||||
|                           {:else}no address specified{/if} |                                 <a | ||||||
|  |                                   href="../orgs/{g.id}" | ||||||
|  |                                   class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{g.name}</a> | ||||||
|  |                               {:else} | ||||||
|  |                                 <a | ||||||
|  |                                   href="../teams/{g.id}" | ||||||
|  |                                   class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{g.parentGroup.name} | ||||||
|  |                                   > | ||||||
|  |                                   {g.name}</a> | ||||||
|  |                               {/if} | ||||||
|  |                             {/each} | ||||||
|  |                           {:else} | ||||||
|  |                             {$_('contact-is-not-a-member-in-any-group')} | ||||||
|  |                           {/if} | ||||||
|                         </div> |                         </div> | ||||||
|                       </div> |                       </div> | ||||||
|                     </div> |                     </div> | ||||||
| @@ -100,59 +103,59 @@ | |||||||
|                   <td class="px-6 py-4 whitespace-nowrap"> |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|                     <div class="flex items-center"> |                     <div class="flex items-center"> | ||||||
|                       <div class="ml-4"> |                       <div class="ml-4"> | ||||||
|                         <div |                         <div class="text-sm font-medium text-gray-900"> | ||||||
|                           class="text-sm font-medium text-gray-900"> |                           {#if t.address.address1 !== null} | ||||||
|                           {#if o.contact} |                             {t.address.address1}<br /> | ||||||
|                             {JSON.stringify(o.contact)} |                             {t.address.address2 || ''}<br /> | ||||||
|                           {:else}no contact specified{/if} |                             {t.address.postalcode} | ||||||
|  |                             {t.address.city} | ||||||
|  |                             {t.address.country} | ||||||
|  |                           {/if} | ||||||
|                         </div> |                         </div> | ||||||
|                       </div> |                       </div> | ||||||
|                     </div> |                     </div> | ||||||
|                   </td> |                   </td> | ||||||
|                   {#if active_deletes[o.id] === true} |                   {#if active_deletes[t.id] === true} | ||||||
|                     <td |                     <td | ||||||
|                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|                       <button |                       <button | ||||||
|                         on:click={() => { |                         on:click={() => { | ||||||
|                           active_deletes[o.id] = false; |                           active_deletes[t.id] = false; | ||||||
|                         }} |                         }} | ||||||
|                         tabindex="0" |                         tabindex="0" | ||||||
|                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel |                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> | ||||||
|                         Delete</button> |  | ||||||
|                       <button |                       <button | ||||||
|                         on:click={() => { |                         on:click={() => { | ||||||
|                           RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false) |                           GroupContactService.groupContactControllerRemove(t.id, false).then( | ||||||
|                             .then((resp) => { |                             (resp) => { | ||||||
|                               current_organizations = current_organizations.filter((obj) => obj.id !== o.id); |                               current_contacts = current_contacts.filter( | ||||||
|  |                                 (obj) => obj.id !== t.id | ||||||
|  |                               ); | ||||||
|                               Toastify({ |                               Toastify({ | ||||||
|                                 text: 'Organization deleted', |                                 text: $_('contact-deleted'), | ||||||
|                                 duration: 500, |                                 duration: 500, | ||||||
|                                 backgroundColor: |                                 backgroundColor: | ||||||
|                                   'linear-gradient(to right, #00b09b, #96c93d)', |                                   'linear-gradient(to right, #00b09b, #96c93d)', | ||||||
|                               }).showToast(); |                               }).showToast(); | ||||||
|                             }) |                             } | ||||||
|                             .catch((err) => { |                           ); | ||||||
|                               modal_open = true; |  | ||||||
|                               delete_org = o; |  | ||||||
|                             }); |  | ||||||
|                         }} |                         }} | ||||||
|                         tabindex="0" |                         tabindex="0" | ||||||
|                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">Confirm |                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> | ||||||
|                         Delete</button> |  | ||||||
|                     </td> |                     </td> | ||||||
|                   {:else} |                   {:else} | ||||||
|                     <td |                     <td | ||||||
|                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|                       <a |                       <a | ||||||
|                         href="./{o.id}" |                         href="./{t.id}" | ||||||
|                         class="text-indigo-600 hover:text-indigo-900">Edit</a> |                         class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> | ||||||
|                       {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')} |                       {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')} | ||||||
|                         <button |                         <button | ||||||
|                           on:click={() => { |                           on:click={() => { | ||||||
|                             active_deletes[o.id] = true; |                             active_deletes[t.id] = true; | ||||||
|                           }} |                           }} | ||||||
|                           tabindex="0" |                           tabindex="0" | ||||||
|                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">Delete</button> |                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> | ||||||
|                       {/if} |                       {/if} | ||||||
|                     </td> |                     </td> | ||||||
|                   {/if} |                   {/if} | ||||||
| @@ -1,11 +1,10 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   import localForage from "localforage"; |   import localForage from "localforage"; | ||||||
|   import store from "../store"; |   import store from "../../store"; | ||||||
|   import { router } from "tinro"; |   import { router } from "tinro"; | ||||||
|   import NoComponentLoaded from "./NoComponentLoaded.svelte"; |   import NoComponentLoaded from "../base/NoComponentLoaded.svelte"; | ||||||
|   import { AuthService } from "@odit/lfk-client-js"; |   import { AuthService } from "@odit/lfk-client-js"; | ||||||
|   let dropdown1 = false; |  | ||||||
|   $: navOpen = false; |   $: navOpen = false; | ||||||
|   function logout() { |   function logout() { | ||||||
|     localForage.clear(); |     localForage.clear(); | ||||||
| @@ -59,6 +58,23 @@ | |||||||
|           class:bg-gray-100={$router.path === '/users/'} |           class:bg-gray-100={$router.path === '/users/'} | ||||||
|           class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" |           class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" | ||||||
|           href="/users/"> |           href="/users/"> | ||||||
|  |           <svg | ||||||
|  |             xmlns="http://www.w3.org/2000/svg" | ||||||
|  |             width="24" | ||||||
|  |             height="24" | ||||||
|  |             class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" | ||||||
|  |             fill="currentColor" | ||||||
|  |             viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |             <path | ||||||
|  |               d="M12 14v8H4a8 8 0 018-8zm0-1a6 6 0 110-12 6 6 0 010 12zm2.6 5.81a3.51 3.51 0 010-1.62l-1-.57 1-1.74 1 .58a3.5 3.5 0 011.4-.82V13.5h2v1.15a3.5 3.5 0 011.4.8l1-.57 1 1.74-1 .57a3.51 3.51 0 010 1.62l1 .57-1 1.74-1-.58a3.5 3.5 0 01-1.4.82v1.14h-2v-1.15a3.5 3.5 0 01-1.4-.8l-1 .57-1-1.74 1-.57zM18 17a1 1 0 100 2 1 1 0 000-2z" /></svg> | ||||||
|  |           <span>{$_('users')}</span> | ||||||
|  |         </a> | ||||||
|  |       {/if} | ||||||
|  |       {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')} | ||||||
|  |         <a | ||||||
|  |           class:bg-gray-100={$router.path === '/groups/'} | ||||||
|  |           class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" | ||||||
|  |           href="/groups/"> | ||||||
|           <svg |           <svg | ||||||
|             class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" |             class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" | ||||||
|             fill="currentColor" |             fill="currentColor" | ||||||
| @@ -68,7 +84,7 @@ | |||||||
|             viewBox="0 0 640 512"><path |             viewBox="0 0 640 512"><path | ||||||
|               fill="currentColor" |               fill="currentColor" | ||||||
|               d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> |               d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> | ||||||
|           <span>{$_('users')}</span> |           <span>{$_('user-groups')}</span> | ||||||
|         </a> |         </a> | ||||||
|       {/if} |       {/if} | ||||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} |       {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} | ||||||
| @@ -105,6 +121,40 @@ | |||||||
|           <span>{$_('teams')}</span> |           <span>{$_('teams')}</span> | ||||||
|         </a> |         </a> | ||||||
|       {/if} |       {/if} | ||||||
|  |       {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')} | ||||||
|  |         <a | ||||||
|  |           class:bg-gray-100={$router.path.includes('/donors/')} | ||||||
|  |           class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" | ||||||
|  |           href="/donors/"> | ||||||
|  |           <svg | ||||||
|  |             class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" | ||||||
|  |             fill="currentColor" | ||||||
|  |             xmlns="http://www.w3.org/2000/svg" | ||||||
|  |             viewBox="0 0 24 24" | ||||||
|  |             width="24" | ||||||
|  |             height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |             <path | ||||||
|  |               d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg> | ||||||
|  |           <span>{$_('donors')}</span> | ||||||
|  |         </a> | ||||||
|  |       {/if} | ||||||
|  |       {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')} | ||||||
|  |         <a | ||||||
|  |           class:bg-gray-100={$router.path.includes('/donations/')} | ||||||
|  |           class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" | ||||||
|  |           href="/donations/"> | ||||||
|  |           <svg | ||||||
|  |             class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" | ||||||
|  |             fill="currentColor" | ||||||
|  |             xmlns="http://www.w3.org/2000/svg" | ||||||
|  |             viewBox="0 0 24 24" | ||||||
|  |             width="24" | ||||||
|  |             height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |             <path | ||||||
|  |               d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg> | ||||||
|  |           <span>{$_('donations')}</span> | ||||||
|  |         </a> | ||||||
|  |       {/if} | ||||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')} |       {#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')} | ||||||
|         <a |         <a | ||||||
|           class:bg-gray-100={$router.path === '/tracks/'} |           class:bg-gray-100={$router.path === '/tracks/'} | ||||||
| @@ -122,6 +172,61 @@ | |||||||
|           <span>{$_('tracks')}</span> |           <span>{$_('tracks')}</span> | ||||||
|         </a> |         </a> | ||||||
|       {/if} |       {/if} | ||||||
|  |       {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')} | ||||||
|  |         <a | ||||||
|  |           class:bg-gray-100={$router.path === '/scans/'} | ||||||
|  |           class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" | ||||||
|  |           href="/scans/"> | ||||||
|  |           <svg | ||||||
|  |             class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" | ||||||
|  |             fill="currentColor" | ||||||
|  |             width="24" | ||||||
|  |             height="24" | ||||||
|  |             xmlns="http://www.w3.org/2000/svg" | ||||||
|  |             viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |             <path | ||||||
|  |               fill="currentColor" | ||||||
|  |               d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> | ||||||
|  |           <span>Scans</span> | ||||||
|  |         </a> | ||||||
|  |       {/if} | ||||||
|  |       {#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')} | ||||||
|  |         <a | ||||||
|  |           class:bg-gray-100={$router.path === '/contacts/'} | ||||||
|  |           class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" | ||||||
|  |           href="/contacts/"> | ||||||
|  |           <svg | ||||||
|  |             fill="currentColor" | ||||||
|  |             class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" | ||||||
|  |             xmlns="http://www.w3.org/2000/svg" | ||||||
|  |             viewBox="0 0 24 24" | ||||||
|  |             width="24" | ||||||
|  |             height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |             <path | ||||||
|  |               d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg> | ||||||
|  |           <span>{$_('contacts')}</span> | ||||||
|  |         </a> | ||||||
|  |       {/if} | ||||||
|  |       {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')} | ||||||
|  |         <a | ||||||
|  |           class:bg-gray-100={$router.path === '/scanstations/'} | ||||||
|  |           class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" | ||||||
|  |           href="/scanstations/"> | ||||||
|  |           <svg | ||||||
|  |             class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" | ||||||
|  |             fill="currentColor" | ||||||
|  |             width="24" | ||||||
|  |             height="24" | ||||||
|  |             viewBox="0 0 24 24" | ||||||
|  |             xmlns="http://www.w3.org/2000/svg"><path | ||||||
|  |               fill="none" | ||||||
|  |               d="M0 0h24v24H0z" /> | ||||||
|  |             <path | ||||||
|  |               fill="currentColor" | ||||||
|  |               d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> | ||||||
|  |           <span>{$_('scanstations')}</span> | ||||||
|  |         </a> | ||||||
|  |       {/if} | ||||||
|       <a |       <a | ||||||
|         class:bg-gray-100={$router.path === '/settings/'} |         class:bg-gray-100={$router.path === '/settings/'} | ||||||
|         class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" |         class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" | ||||||
| @@ -175,10 +280,21 @@ | |||||||
|     </nav> |     </nav> | ||||||
|   </nav> |   </nav> | ||||||
|   <div class="ml-0 transition md:ml-60"> |   <div class="ml-0 transition md:ml-60"> | ||||||
|     <header on:click={() => { |     <header | ||||||
|       navOpen = true; |       on:click={() => { | ||||||
|     }} class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden"><button class="block btn btn-light md:hidden"> |         navOpen = true; | ||||||
|       <span class="sr-only">Menu</span><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentcolor"><path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path></svg></button></header> |       }} | ||||||
|  |       class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden"> | ||||||
|  |       <button class="block btn btn-light md:hidden"> | ||||||
|  |         <span class="sr-only">Menu</span><svg | ||||||
|  |           class="w-4 h-4" | ||||||
|  |           xmlns="http://www.w3.org/2000/svg" | ||||||
|  |           viewBox="0 0 20 20" | ||||||
|  |           fill="currentcolor"><path | ||||||
|  |             fill-rule="evenodd" | ||||||
|  |             d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" | ||||||
|  |             clip-rule="evenodd" /></svg></button> | ||||||
|  |     </header> | ||||||
|     <slot> |     <slot> | ||||||
|       <NoComponentLoaded /> |       <NoComponentLoaded /> | ||||||
|     </slot> |     </slot> | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   import StatCards from "./StatCards.svelte"; |   import StatCards from "./StatCards.svelte"; | ||||||
|   import store from "../store"; |   import store from "../../store"; | ||||||
|   let navOpen = false; |   let navOpen = false; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| @@ -10,14 +10,13 @@ | |||||||
|   on:click={() => { |   on:click={() => { | ||||||
|     navOpen = false; |     navOpen = false; | ||||||
|   }}> |   }}> | ||||||
|   <!-- <div class="border-4 border-dashed rounded h-96" /> --> |  | ||||||
|   <h1 class="text-3xl leading-tight"> |   <h1 class="text-3xl leading-tight"> | ||||||
|     <span class="font-extrabold">{$_('dashboard-title')}</span> <span> |     <span class="font-extrabold">{$_('dashboard-title')}</span> | ||||||
|  |     <span> | ||||||
|       - |       - | ||||||
|       {$_('dashboard-greeting')}, |       {$_('dashboard-greeting')}, | ||||||
|       <span |       <span | ||||||
|         class="text-blue-500">{store.state.jwtinfo.userdetails.firstname}</span> |         class="text-blue-500">{store.state.jwtinfo.userdetails.firstname} {store.state.jwtinfo.userdetails.lastname}</span></span> | ||||||
|       👋</span> |  | ||||||
|   </h1> |   </h1> | ||||||
|   <StatCards /> |   <StatCards /> | ||||||
| </div> | </div> | ||||||
							
								
								
									
										295
									
								
								src/components/donations/AddDonationModal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,295 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { clickOutside } from "../base/outsideclick"; | ||||||
|  |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|  |   import { | ||||||
|  |     DonationService, | ||||||
|  |     DonorService, | ||||||
|  |     RunnerService, | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   export let modal_open; | ||||||
|  |   export let current_donations; | ||||||
|  |   const getDonorLabel = (option) => | ||||||
|  |     option.firstname + " " + (option.middlename || "") + " " + option.lastname; | ||||||
|  |   const filterDonors = (label, filterText, option) => | ||||||
|  |     label.toLowerCase().includes(filterText.toLowerCase()) || | ||||||
|  |     option.value.id.toString().startsWith(filterText.toLowerCase()); | ||||||
|  |   function focus(el) { | ||||||
|  |     el.focus(); | ||||||
|  |   } | ||||||
|  |   $: donor = 0; | ||||||
|  |   $: runner = 0; | ||||||
|  |   $: donors = []; | ||||||
|  |   $: runners = []; | ||||||
|  |   $: is_fixed = false; | ||||||
|  |   DonorService.donorControllerGetAll().then((val) => { | ||||||
|  |     donors = val.map((r) => { | ||||||
|  |       return { label: getDonorLabel(r), value: r }; | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   RunnerService.runnerControllerGetAll().then((val) => { | ||||||
|  |     runners = val.map((r) => { | ||||||
|  |       return { label: getDonorLabel(r), value: r }; | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   $: amount_input = 0; | ||||||
|  |   $: processed_last_submit = true; | ||||||
|  |   $: is_amount_valid = amount_input > 0; | ||||||
|  |   $: createbtnenabled = is_amount_valid; | ||||||
|  |   (() => { | ||||||
|  |     document.onkeydown = (e) => { | ||||||
|  |       e = e || window.event; | ||||||
|  |       if (e.key === "Escape") { | ||||||
|  |         modal_open = false; | ||||||
|  |       } | ||||||
|  |       if (e.keyCode === 13) { | ||||||
|  |         if (createbtnenabled === true) { | ||||||
|  |           createbtnenabled = false; | ||||||
|  |           submit(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   })(); | ||||||
|  |   function submit() { | ||||||
|  |     if (processed_last_submit === true) { | ||||||
|  |       let amount_cent = Math.floor(amount_input * 100); | ||||||
|  |       processed_last_submit = false; | ||||||
|  |       const toast = Toastify({ | ||||||
|  |         text: "adding donation", | ||||||
|  |         duration: -1, | ||||||
|  |       }).showToast(); | ||||||
|  |       if (is_fixed) { | ||||||
|  |         let postdata = { | ||||||
|  |           donor, | ||||||
|  |           amount: amount_cent, | ||||||
|  |         }; | ||||||
|  |         DonationService.donationControllerPostFixed(postdata) | ||||||
|  |           .then((result) => { | ||||||
|  |             donor = donors[0].id || 0; | ||||||
|  |             runner = runners[0].id || 0; | ||||||
|  |             amount_input = 0; | ||||||
|  |             modal_open = false; | ||||||
|  |             // | ||||||
|  |             Toastify({ | ||||||
|  |               text: "donation_added", | ||||||
|  |               duration: 500, | ||||||
|  |               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |             }).showToast(); | ||||||
|  |             current_donations.push(result); | ||||||
|  |             current_donations = current_donations; | ||||||
|  |           }) | ||||||
|  |           .catch((err) => { | ||||||
|  |             // | ||||||
|  |           }) | ||||||
|  |           .finally(() => { | ||||||
|  |             processed_last_submit = true; | ||||||
|  |             // | ||||||
|  |             toast.hideToast(); | ||||||
|  |           }); | ||||||
|  |       } else { | ||||||
|  |         let postdata = { | ||||||
|  |           donor, | ||||||
|  |           runner, | ||||||
|  |           amountPerDistance: amount_cent, | ||||||
|  |         }; | ||||||
|  |         DonationService.donationControllerPostDistance(postdata) | ||||||
|  |           .then((result) => { | ||||||
|  |             donor = donors[0].id || 0; | ||||||
|  |             runner = runners[0].id || 0; | ||||||
|  |             amount_input = 0; | ||||||
|  |             modal_open = false; | ||||||
|  |             // | ||||||
|  |             Toastify({ | ||||||
|  |               text: "donation_added", | ||||||
|  |               duration: 500, | ||||||
|  |               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |             }).showToast(); | ||||||
|  |             current_donations.push(result); | ||||||
|  |             current_donations = current_donations; | ||||||
|  |           }) | ||||||
|  |           .catch((err) => { | ||||||
|  |             // | ||||||
|  |           }) | ||||||
|  |           .finally(() => { | ||||||
|  |             processed_last_submit = true; | ||||||
|  |             // | ||||||
|  |             toast.hideToast(); | ||||||
|  |           }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style> | ||||||
|  |   input:before { | ||||||
|  |     content: ""; | ||||||
|  |     position: absolute; | ||||||
|  |     width: 1.25rem; | ||||||
|  |     height: 1.25rem; | ||||||
|  |     border-radius: 50%; | ||||||
|  |     top: 0; | ||||||
|  |     left: 0; | ||||||
|  |     transform: scale(1.1); | ||||||
|  |     box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.2); | ||||||
|  |     background-color: white; | ||||||
|  |     transition: 0.2s ease-in-out; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   input:checked { | ||||||
|  |     /* @apply: bg-indigo-400; */ | ||||||
|  |     background-color: #7f9cf5; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   input:checked:before { | ||||||
|  |     left: 1.25rem; | ||||||
|  |   } | ||||||
|  | </style> | ||||||
|  |  | ||||||
|  | {#if modal_open} | ||||||
|  |   <div | ||||||
|  |     class="fixed z-10 inset-0 overflow-y-auto" | ||||||
|  |     use:focusTrap | ||||||
|  |     use:clickOutside | ||||||
|  |     on:click_outside={() => { | ||||||
|  |       modal_open = false; | ||||||
|  |     }}> | ||||||
|  |     <div | ||||||
|  |       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||||
|  |       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||||
|  |         <div | ||||||
|  |           class="absolute inset-0 bg-gray-500 opacity-75" | ||||||
|  |           data-id="modal_backdrop" /> | ||||||
|  |       </div> | ||||||
|  |       <span | ||||||
|  |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|  |         aria-hidden="true">​</span> | ||||||
|  |       <div | ||||||
|  |         class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|  |         role="dialog" | ||||||
|  |         aria-modal="true" | ||||||
|  |         aria-labelledby="modal-headline"> | ||||||
|  |         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||||
|  |           <div class="sm:flex sm:items-start"> | ||||||
|  |             <div | ||||||
|  |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|  |               <svg | ||||||
|  |                 class="h-6 w-6 text-blue-600" | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|  |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |                 {#if is_fixed} | ||||||
|  |                   {$_('create-a-new-fixed-donation')} | ||||||
|  |                 {:else}{$_('create-a-new-distance-donation')}{/if} | ||||||
|  |               </h3> | ||||||
|  |               <label class="content-center align-middle object-center"> | ||||||
|  |                 <span | ||||||
|  |                   class="ml-2 text-base" | ||||||
|  |                   class:text-gray-300={is_fixed}>{$_('distance-donation')}</span> | ||||||
|  |                 <input | ||||||
|  |                   class="relative w-10 h-5 transition-all duration-200 ease-in-out bg-gray-400 rounded-full shadow-inner outline-none appearance-none align-middle" | ||||||
|  |                   type="checkbox" | ||||||
|  |                   bind:checked={is_fixed} /> | ||||||
|  |                 <span | ||||||
|  |                   class="ml-2 text-base	" | ||||||
|  |                   class:text-gray-300={!is_fixed}>{$_('fixed-donation')}</span> | ||||||
|  |               </label> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <p class="text-sm text-gray-500"> | ||||||
|  |                   {$_('please-provide-the-nessecary-information-to-create-a-new-donation')} | ||||||
|  |                 </p> | ||||||
|  |               </div> | ||||||
|  |               <div class="grid grid-cols-6 gap-6"> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="donor" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('donor')}</label> | ||||||
|  |                   <Select | ||||||
|  |                     containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|  |                     itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)} | ||||||
|  |                     items={donors} | ||||||
|  |                     showChevron={true} | ||||||
|  |                     placeholder={$_('search-for-donor-name-or-id')} | ||||||
|  |                     noOptionsMessage={$_('no-donors-found')} | ||||||
|  |                     on:select={(selectedValue) => (donor = selectedValue.detail.value.id)} | ||||||
|  |                     on:clear={() => (donors = null)} /> | ||||||
|  |                 </div> | ||||||
|  |                 {#if !is_fixed} | ||||||
|  |                   <div class="col-span-6"> | ||||||
|  |                     <label | ||||||
|  |                       for="donor" | ||||||
|  |                       class="block text-sm font-medium text-gray-700">{$_('runner')}</label> | ||||||
|  |                     <Select | ||||||
|  |                       containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|  |                       itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)} | ||||||
|  |                       items={runners} | ||||||
|  |                       showChevron={true} | ||||||
|  |                       placeholder={$_('search-for-runner-by-name-or-id')} | ||||||
|  |                       noOptionsMessage={$_('no-runners-found')} | ||||||
|  |                       on:select={(selectedValue) => (runner = selectedValue.detail.value.id)} | ||||||
|  |                       on:clear={() => (runner = null)} /> | ||||||
|  |                   </div> | ||||||
|  |                 {/if} | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="donation_amount_eur" | ||||||
|  |                     class="block text-sm font-medium text-gray-700"> | ||||||
|  |                     {#if !is_fixed} | ||||||
|  |                       {$_('amount-per-kilometer')} | ||||||
|  |                     {:else}{$_('donation-amount')}{/if}</label> | ||||||
|  |                   <div class="mt-1 flex rounded-md shadow-sm"> | ||||||
|  |                     <input | ||||||
|  |                       autocomplete="off" | ||||||
|  |                       class:border-red-500={!is_amount_valid} | ||||||
|  |                       class:focus:border-red-500={!is_amount_valid} | ||||||
|  |                       class:focus:ring-red-500={!is_amount_valid} | ||||||
|  |                       bind:value={amount_input} | ||||||
|  |                       type="number" | ||||||
|  |                       step="0.01" | ||||||
|  |                       name="donation_amount_eur" | ||||||
|  |                       class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2" | ||||||
|  |                       placeholder="2.00" /> | ||||||
|  |                     <span | ||||||
|  |                       class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">€</span> | ||||||
|  |                   </div> | ||||||
|  |                   {#if !is_amount_valid} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('donation-amount-must-be-greater-that-0-00eur')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||||
|  |           <button | ||||||
|  |             disabled={!createbtnenabled} | ||||||
|  |             class:opacity-50={!createbtnenabled} | ||||||
|  |             on:click={submit} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('create')} | ||||||
|  |           </button> | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |               modal_open = false; | ||||||
|  |             }} | ||||||
|  |             type="button" | ||||||
|  |             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('cancel')} | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
							
								
								
									
										286
									
								
								src/components/donations/DonationDetail.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,286 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import { | ||||||
|  |     DonationService, | ||||||
|  |     DonorService, | ||||||
|  |     RunnerService, | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import PromiseError from "../base/PromiseError.svelte"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|  |   let data_loaded = false; | ||||||
|  |   export let params; | ||||||
|  |   $: delete_triggered = false; | ||||||
|  |   $: original_data = {}; | ||||||
|  |   $: editable = {}; | ||||||
|  |   $: donor = {}; | ||||||
|  |   $: runner = {}; | ||||||
|  |   $: current_donors = []; | ||||||
|  |   $: current_runners = []; | ||||||
|  |   $: amount_input = 0; | ||||||
|  |   $: is_amount_valid = amount_input > 0; | ||||||
|  |   $: is_everything_set = | ||||||
|  |     editable.donor != null && | ||||||
|  |     ((original_data.responseType == "DISTANCEDONATION" && | ||||||
|  |       editable?.runner != null) || | ||||||
|  |       original_data.responseType !== "DISTANCEDONATION"); | ||||||
|  |   $: changes_performed = | ||||||
|  |     !(JSON.stringify(original_data) === JSON.stringify(editable)) || | ||||||
|  |     (original_data.responseType == "DISTANCEDONATION" && | ||||||
|  |       !(Math.floor(amount_input * 100) === original_data.amountPerDistance)) || | ||||||
|  |     (original_data.responseType !== "DISTANCEDONATION" && | ||||||
|  |       !(Math.floor(amount_input * 100) === original_data.amount)); | ||||||
|  |   $: save_enabled = changes_performed && is_amount_valid && is_everything_set; | ||||||
|  |  | ||||||
|  |   const promise = DonationService.donationControllerGetOne( | ||||||
|  |     params.donationid | ||||||
|  |   ).then((data) => { | ||||||
|  |     data_loaded = true; | ||||||
|  |     original_data = Object.assign(original_data, data); | ||||||
|  |     editable = Object.assign(editable, original_data); | ||||||
|  |     if (data.responseType == "DISTANCEDONATION") { | ||||||
|  |       amount_input = data.amountPerDistance / 100; | ||||||
|  |       RunnerService.runnerControllerGetAll().then((val) => { | ||||||
|  |         current_runners = val.map((r) => { | ||||||
|  |           return { label: getDonorLabel(r), value: r }; | ||||||
|  |         }); | ||||||
|  |         runner = current_runners.find((g) => g.value.id == editable.runner.id); | ||||||
|  |       }); | ||||||
|  |     } else { | ||||||
|  |       amount_input = data.amount / 100; | ||||||
|  |     } | ||||||
|  |     DonorService.donorControllerGetAll().then((val) => { | ||||||
|  |       current_donors = val.map((r) => { | ||||||
|  |         return { label: getDonorLabel(r), value: r }; | ||||||
|  |       }); | ||||||
|  |       donor = current_donors.find((g) => g.value.id == editable.donor.id); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   const getDonorLabel = (option) => | ||||||
|  |     option.firstname + " " + (option.middlename || "") + " " + option.lastname; | ||||||
|  |   const filterDonors = (label, filterText, option) => | ||||||
|  |     label.toLowerCase().includes(filterText.toLowerCase()) || | ||||||
|  |     option.value.id.toString().startsWith(filterText.toLowerCase()); | ||||||
|  |  | ||||||
|  |   function submit() { | ||||||
|  |     if (data_loaded === true && save_enabled) { | ||||||
|  |       Toastify({ | ||||||
|  |         text: "Donation is being updated", | ||||||
|  |         duration: 2500, | ||||||
|  |       }).showToast(); | ||||||
|  |       let postdata = {}; | ||||||
|  |       if (original_data.responseType === "DISTANCEDONATION") { | ||||||
|  |         editable.amountPerDistance = Math.floor(amount_input * 100); | ||||||
|  |         postdata = Object.assign(postdata, editable); | ||||||
|  |         postdata.runner = postdata.runner.id; | ||||||
|  |         postdata.donor = postdata.donor.id; | ||||||
|  |         DonationService.donationControllerPutDistance( | ||||||
|  |           original_data.id, | ||||||
|  |           postdata | ||||||
|  |         ) | ||||||
|  |           .then((resp) => { | ||||||
|  |             Object.assign(original_data, editable); | ||||||
|  |             original_data = original_data; | ||||||
|  |             Toastify({ | ||||||
|  |               text: "updated donation", | ||||||
|  |               duration: 2500, | ||||||
|  |               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |             }).showToast(); | ||||||
|  |           }) | ||||||
|  |           .catch((err) => {}); | ||||||
|  |       } else { | ||||||
|  |         editable.amount = Math.floor(amount_input * 100); | ||||||
|  |         postdata = Object.assign(postdata, editable); | ||||||
|  |         postdata.donor = postdata.donor.id; | ||||||
|  |         DonationService.donationControllerPutFixed(original_data.id, postdata) | ||||||
|  |           .then((resp) => { | ||||||
|  |             Object.assign(original_data, editable); | ||||||
|  |             original_data = original_data; | ||||||
|  |             Toastify({ | ||||||
|  |               text: "updated donation", | ||||||
|  |               duration: 2500, | ||||||
|  |               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |             }).showToast(); | ||||||
|  |           }) | ||||||
|  |           .catch((err) => {}); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   function deleteDonation() { | ||||||
|  |     DonationService.donationControllerRemove(original_data.id, false) | ||||||
|  |       .then((resp) => { | ||||||
|  |         Toastify({ | ||||||
|  |           text: "Donation delete", | ||||||
|  |           duration: 500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |         location.replace("./"); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => { | ||||||
|  |         modal_open = true; | ||||||
|  |         delete_donor = original_data; | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#await promise} | ||||||
|  |   {$_('loading-donation-details')} | ||||||
|  | {:then} | ||||||
|  |   <section class="container p-5 select-none"> | ||||||
|  |     <div class="flex flex-row mb-4"> | ||||||
|  |       <div class="w-full"> | ||||||
|  |         <nav class="w-full flex"> | ||||||
|  |           <ol class="list-none flex flex-row items-center justify-start"> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <svg | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center ml-2"> | ||||||
|  |               <a class="mr-2" href="./">{$_('donations')}</a><svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 mr-2 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><line | ||||||
|  |                   x1="5" | ||||||
|  |                   y1="12" | ||||||
|  |                   x2="19" | ||||||
|  |                   y2="12" /> | ||||||
|  |                 <polyline points="12 5 19 12 12 19" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <span class="mr-2">{original_data.id}</span> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </nav> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="mb-8 text-3xl font-extrabold leading-tight"> | ||||||
|  |       {original_data.donor.firstname} | ||||||
|  |       {original_data.donor.middlename || ''} | ||||||
|  |       {original_data.donor.lastname} | ||||||
|  |       > | ||||||
|  |       {#if original_data.responseType == 'DISTANCEDONATION'} | ||||||
|  |         {original_data.runner.firstname} | ||||||
|  |         {original_data.runner.middlename || ''} | ||||||
|  |         {original_data.runner.lastname} | ||||||
|  |       {:else} | ||||||
|  |         {$_('fixed-donation')}: | ||||||
|  |         {amount_input.toFixed(2).toLocaleString('de-DE', { valute: 'EUR' })}€ | ||||||
|  |       {/if} | ||||||
|  |       <span data-id="donation_actions_${original_data.id}"> | ||||||
|  |         {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:DELETE')} | ||||||
|  |           {#if delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={deleteDonation} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:">{$_('confirm-deletion')}</button> | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = !delete_triggered; | ||||||
|  |               }} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:">{$_('cancel')}</button> | ||||||
|  |           {/if} | ||||||
|  |           {#if !delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = true; | ||||||
|  |               }} | ||||||
|  |               type="button" | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:">{$_('delete-donation')}</button> | ||||||
|  |           {/if} | ||||||
|  |         {/if} | ||||||
|  |         {#if !delete_triggered} | ||||||
|  |           <button | ||||||
|  |             disabled={!save_enabled} | ||||||
|  |             class:opacity-50={!save_enabled} | ||||||
|  |             type="button" | ||||||
|  |             on:click={submit} | ||||||
|  |             class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:">{$_('save-changes')}</button> | ||||||
|  |         {/if} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |     <!--  --> | ||||||
|  |     <div> | ||||||
|  |       <span | ||||||
|  |         class="font-medium text-gray-700">{$_('total-donation-amount')}:</span> | ||||||
|  |       <span>{(editable.amount / 100) | ||||||
|  |           .toFixed(2) | ||||||
|  |           .toLocaleString('de-DE', { valute: 'EUR' })}€</span> | ||||||
|  |     </div> | ||||||
|  |     <div class=" w-full"> | ||||||
|  |       <label | ||||||
|  |         for="donor" | ||||||
|  |         class="block  font-medium text-gray-700">{$_('donor')}</label> | ||||||
|  |       <Select | ||||||
|  |         containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|  |         itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)} | ||||||
|  |         items={current_donors} | ||||||
|  |         showChevron={true} | ||||||
|  |         placeholder={$_('search-for-donor-name-or-id')} | ||||||
|  |         noOptionsMessage={$_('no-donors-found')} | ||||||
|  |         bind:selectedValue={donor} | ||||||
|  |         on:select={(selectedValue) => (editable.donor = selectedValue.detail.value)} | ||||||
|  |         on:clear={() => (editable.donor = null)} /> | ||||||
|  |     </div> | ||||||
|  |     {#if original_data.responseType == 'DISTANCEDONATION'} | ||||||
|  |       <div class=" w-full"> | ||||||
|  |         <label | ||||||
|  |           for="donor" | ||||||
|  |           class="block  font-medium text-gray-700">{$_('runner')}</label> | ||||||
|  |         <Select | ||||||
|  |           containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|  |           itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)} | ||||||
|  |           items={current_runners} | ||||||
|  |           showChevron={true} | ||||||
|  |           placeholder={$_('search-for-runner-by-name-or-id')} | ||||||
|  |           noOptionsMessage={$_('no-runners-found')} | ||||||
|  |           bind:selectedValue={runner} | ||||||
|  |           on:select={(selectedValue) => (editable.runner = selectedValue.detail.value)} | ||||||
|  |           on:clear={() => (editable.runner = null)} /> | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |     <div class=" w-full"> | ||||||
|  |       <label for="lastname" class="font-medium text-gray-700"> | ||||||
|  |         {#if original_data.responseType == 'DISTANCEDONATION'} | ||||||
|  |           {$_('amount-per-kilometer')} | ||||||
|  |         {:else}{$_('donation-amount')}{/if} | ||||||
|  |       </label> | ||||||
|  |       <div class="mt-1 flex rounded-md shadow-sm"> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           class:border-red-500={!is_amount_valid} | ||||||
|  |           class:focus:border-red-500={!is_amount_valid} | ||||||
|  |           class:focus:ring-red-500={!is_amount_valid} | ||||||
|  |           bind:value={amount_input} | ||||||
|  |           type="number" | ||||||
|  |           step="0.01" | ||||||
|  |           name="donation_amount_eur" | ||||||
|  |           class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 p-2" | ||||||
|  |           placeholder="2.00" /> | ||||||
|  |         <span | ||||||
|  |           class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 ">€</span> | ||||||
|  |       </div> | ||||||
|  |       {#if !is_amount_valid} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('donation-amount-must-be-greater-that-0-00eur')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |   </section> | ||||||
|  | {:catch error} | ||||||
|  |   <PromiseError {error} /> | ||||||
|  | {/await} | ||||||
							
								
								
									
										29
									
								
								src/components/donations/Donations.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import AddDonationModal from "./AddDonationModal.svelte"; | ||||||
|  |   import DonationsOverview from "./DonationsOverview.svelte"; | ||||||
|  |   $: current_donations = []; | ||||||
|  |   export let modal_open = false; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <section class="container p-5"> | ||||||
|  |   <span class="mb-1 text-3xl font-extrabold leading-tight"> | ||||||
|  |     {$_('donations')} | ||||||
|  |     {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:CREATE')} | ||||||
|  |       <button | ||||||
|  |         on:click={() => { | ||||||
|  |           modal_open = true; | ||||||
|  |         }} | ||||||
|  |         type="button" | ||||||
|  |         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |         {$_('add-donation')} | ||||||
|  |       </button> | ||||||
|  |     {/if} | ||||||
|  |   </span> | ||||||
|  |   <DonationsOverview bind:current_donations /> | ||||||
|  | </section> | ||||||
|  |  | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:CREATE')} | ||||||
|  |   <AddDonationModal bind:current_donations bind:modal_open /> | ||||||
|  | {/if} | ||||||
							
								
								
									
										12
									
								
								src/components/donations/DonationsEmptyState.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import donations_empty from "./donations.svg"; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div class="text-center items-center justify-center"> | ||||||
|  |   <p class="mb-16 text-lg text-gray-500"> | ||||||
|  |     <img class="m-auto" style="height:15rem" src={donations_empty} alt="" /> | ||||||
|  |     <span class="font-bold">{$_('there-are-no-donations-yet')}</span><br /> | ||||||
|  |     <span>{$_('add-your-fist-donation')}</span> | ||||||
|  |   </p> | ||||||
|  | </div> | ||||||
							
								
								
									
										202
									
								
								src/components/donations/DonationsOverview.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,202 @@ | |||||||
|  | <script> | ||||||
|  |   import { getLocaleFromNavigator, _ } from "svelte-i18n"; | ||||||
|  |   import { DonationService, DonorService } from "@odit/lfk-client-js"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import DonationsEmptyState from "./DonationsEmptyState.svelte"; | ||||||
|  |   $: searchvalue = ""; | ||||||
|  |   $: active_deletes = []; | ||||||
|  |   export let current_donations = []; | ||||||
|  |   const donations_promise = DonationService.donationControllerGetAll().then( | ||||||
|  |     (val) => { | ||||||
|  |       current_donations = val; | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   function should_display_based_on_id(id) { | ||||||
|  |     if (searchvalue.toString().slice(-1) === "*") { | ||||||
|  |       return id.toString().startsWith(searchvalue.replace("*", "")); | ||||||
|  |     } | ||||||
|  |     return id.toString() === searchvalue; | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')} | ||||||
|  |   {#await donations_promise} | ||||||
|  |     <div | ||||||
|  |       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||||
|  |       role="alert"> | ||||||
|  |       <p class="font-bold">donations are being loaded</p> | ||||||
|  |       <p class="text-sm">{$_('this-might-take-a-moment')}</p> | ||||||
|  |     </div> | ||||||
|  |   {:then} | ||||||
|  |     {#if current_donations.length === 0} | ||||||
|  |       <DonationsEmptyState /> | ||||||
|  |     {:else} | ||||||
|  |       <input | ||||||
|  |         type="search" | ||||||
|  |         bind:value={searchvalue} | ||||||
|  |         placeholder={$_('datatable.search')} | ||||||
|  |         aria-label={$_('datatable.search')} | ||||||
|  |         class="gridjs-input gridjs-search-input mb-4" /> | ||||||
|  |       <div | ||||||
|  |         class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> | ||||||
|  |         <table class="divide-y divide-gray-200 w-full"> | ||||||
|  |           <thead class="bg-gray-50"> | ||||||
|  |             <tr> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('donor')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('runner')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('amount-per-kilometer')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('donation-amount')} | ||||||
|  |               </th> | ||||||
|  |               <th scope="col" class="relative px-6 py-3"> | ||||||
|  |                 <span class="sr-only">{$_('action')}</span> | ||||||
|  |               </th> | ||||||
|  |             </tr> | ||||||
|  |           </thead> | ||||||
|  |           <tbody class="divide-y divide-gray-200"> | ||||||
|  |             {#each current_donations as donation} | ||||||
|  |               {#if donation.donor.firstname | ||||||
|  |                 .toLowerCase() | ||||||
|  |                 .includes( | ||||||
|  |                   searchvalue.toLowerCase() | ||||||
|  |                 ) || donation.donor.middlename | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || donation.donor.lastname | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || donation.runner?.firstname | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || donation.runner?.middlename | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || donation.runner?.lastname | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || should_display_based_on_id(donation.id)} | ||||||
|  |                 <tr data-rowid="donation_{donation.id}"> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <div class="flex items-center"> | ||||||
|  |                       <a | ||||||
|  |                         href="../donors/{donation.donor.id}" | ||||||
|  |                         class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{donation.donor.firstname} | ||||||
|  |                         {donation.donor.middlename || ''} | ||||||
|  |                         {donation.donor.lastname}</a> | ||||||
|  |                     </div> | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     {#if donation.runner} | ||||||
|  |                       <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                         <a | ||||||
|  |                           href="../runners/{donation.runner.id}" | ||||||
|  |                           class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{donation.runner.firstname} | ||||||
|  |                           {donation.runner.middlename || ''} | ||||||
|  |                           {donation.runner.lastname}</a> | ||||||
|  |                       </div> | ||||||
|  |                     {:else} | ||||||
|  |                       <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                         {$_('fixed-donation')} | ||||||
|  |                       </div> | ||||||
|  |                     {/if} | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     {#if donation.amountPerDistance} | ||||||
|  |                       <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                         {(donation.amountPerDistance / 100) | ||||||
|  |                           .toFixed(2) | ||||||
|  |                           .toLocaleString('de-DE', { valute: 'EUR' })}€ | ||||||
|  |                       </div> | ||||||
|  |                     {:else} | ||||||
|  |                       <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                         {$_('fixed-donation')} | ||||||
|  |                       </div> | ||||||
|  |                     {/if} | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                       {(donation.amount / 100) | ||||||
|  |                         .toFixed(2) | ||||||
|  |                         .toLocaleString('de-DE', { valute: 'EUR' })}€ | ||||||
|  |                     </div> | ||||||
|  |                   </td> | ||||||
|  |                   {#if active_deletes[donation.id] === true} | ||||||
|  |                     <td | ||||||
|  |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                       <button | ||||||
|  |                         on:click={() => { | ||||||
|  |                           active_deletes[donation.id] = false; | ||||||
|  |                         }} | ||||||
|  |                         tabindex="0" | ||||||
|  |                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> | ||||||
|  |                       <button | ||||||
|  |                         on:click={() => { | ||||||
|  |                           DonationService.donationControllerRemove(donation.id, false).then( | ||||||
|  |                             (resp) => { | ||||||
|  |                               current_donations = current_donations.filter( | ||||||
|  |                                 (obj) => obj.id !== donation.id | ||||||
|  |                               ); | ||||||
|  |                               Toastify({ | ||||||
|  |                                 text: 'Donation deleted', | ||||||
|  |                                 duration: 500, | ||||||
|  |                                 backgroundColor: | ||||||
|  |                                   'linear-gradient(to right, #00b09b, #96c93d)', | ||||||
|  |                               }).showToast(); | ||||||
|  |                             } | ||||||
|  |                           ); | ||||||
|  |                         }} | ||||||
|  |                         tabindex="0" | ||||||
|  |                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> | ||||||
|  |                     </td> | ||||||
|  |                   {:else} | ||||||
|  |                     <td | ||||||
|  |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                       <a | ||||||
|  |                         href="./{donation.id}" | ||||||
|  |                         class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> | ||||||
|  |                       {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:DELETE')} | ||||||
|  |                         <button | ||||||
|  |                           on:click={() => { | ||||||
|  |                             active_deletes[donation.id] = true; | ||||||
|  |                           }} | ||||||
|  |                           tabindex="0" | ||||||
|  |                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> | ||||||
|  |                       {/if} | ||||||
|  |                     </td> | ||||||
|  |                   {/if} | ||||||
|  |                 </tr> | ||||||
|  |               {/if} | ||||||
|  |             {/each} | ||||||
|  |           </tbody> | ||||||
|  |         </table> | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |   {:catch error} | ||||||
|  |     <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> | ||||||
|  |       <span class="inline-block align-middle mr-8"> | ||||||
|  |         <b class="capitalize">{$_('general_promise_error')}</b> | ||||||
|  |         {error} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |   {/await} | ||||||
|  | {/if} | ||||||
							
								
								
									
										1
									
								
								src/components/donations/donations.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="598.11" height="535.11"><path d="M3.35 120.07a4.6 4.6 0 00-3.18 5.66l76.72 273.98a4.6 4.6 0 005.65 3.18l282.82-79.19a4.6 4.6 0 003.18-5.66L291.82 44.07a4.6 4.6 0 00-5.65-3.19z" fill="#e6e6e6"/><path d="M86.1 389.95l269.5-75.46-72.99-260.67-269.5 75.46z" fill="#fff"/><path d="M48.74 164.1c-1.8.5-2.54 3.48-1.65 6.65s3.07 5.33 4.87 4.83l122.91-34.42c1.8-.5 2.54-3.49 1.66-6.65s-3.08-5.34-4.87-4.84zM58.64 199.44c-1.8.5-2.54 3.5-1.65 6.66s3.07 5.34 4.87 4.83l122.91-34.42c1.8-.5 2.54-3.49 1.65-6.65s-3.07-5.34-4.86-4.83zM68.42 234.39c-1.8.5-2.54 3.49-1.65 6.66s3.07 5.33 4.87 4.83l122.92-34.42c1.8-.5 2.54-3.5 1.65-6.66s-3.07-5.33-4.87-4.83zM78.32 269.74c-1.8.5-2.54 3.49-1.65 6.66s3.07 5.33 4.87 4.83l122.92-34.42c1.8-.5 2.54-3.49 1.65-6.66s-3.07-5.33-4.87-4.83zM234.04 112.61a5.97 5.97 0 103.21 11.5l22.98-6.44a5.97 5.97 0 00-3.22-11.49zM243.74 147.28a5.97 5.97 0 103.22 11.49l22.98-6.43a5.97 5.97 0 00-3.22-11.5zM253.45 181.95a5.97 5.97 0 103.22 11.49l22.98-6.44a5.97 5.97 0 00-3.22-11.49zM263.16 216.61a5.97 5.97 0 003.21 11.5l22.98-6.44a5.97 5.97 0 00-3.21-11.49z" fill="#e6e6e6"/><path d="M272.43 276.7a7.6 7.6 0 104.1 14.64l29.28-8.2a7.6 7.6 0 00-4.1-14.64z" fill="#6c63ff"/><path fill="#e6e6e6" d="M85.9 307.81l216.66-60.67.54 1.93-216.67 60.67z"/><path fill="#a0616a" d="M520.2 506.07l-17.38 4.2-24.47-65.02 25.65-6.2 16.2 67.02z"/><path d="M472.85 535.11l-.12-.48a22.23 22.23 0 0116.37-26.8l34-8.23 5.33 22.08z" fill="#2f2e41"/><path fill="#a0616a" d="M443.28 517.91H425.4l-8.5-68.96h26.38v68.96z"/><path d="M447.6 535.01h-57.18v-.5a22.2 22.2 0 0122.2-22.2h34.99zM416.88 490.99l-17.36-206.87 71.86-13.25.28-.05 21.03 13.52-7.32 76.14 33.7 118.7-29.1 7.65-33.75-110.08-7.73-33.48-3.96 43.5 2.94 107.28z" fill="#2f2e41"/><path d="M397.3 288.81l-.2-.24 24.84-186.96.03-.24.17-.18c.37-.36 9.07-8.96 18.02-8.96 1.3 0 2.52-.03 3.7-.06 6.85-.18 12.26-.32 18.69 6.1 6.55 6.56 27.92 30.47 27.92 63.23 0 31.7 2.88 130.22 2.91 131.21l.04 1.4-1.16-.76c-.3-.19-29.03-18.49-53.14-1.48-7.53 5.32-14.3 7.18-20.09 7.18-13.47 0-21.62-10.1-21.73-10.24z" fill="#6c63ff"/><circle cx="737.3" cy="227.82" r="35.82" transform="rotate(-28.66 229.78 725.57)" fill="#a0616a"/><path d="M381.53 328.99a14.66 14.66 0 00.85-22.47l20.34-47.97-26.63 4.9-15.23 44.8A14.74 14.74 0 00381.53 329z" fill="#a0616a"/><path d="M361.88 291.67l6.55-13.83a2.7 2.7 0 01-.97-1c-6.12-10.6 30.84-98.67 33.3-104.51-.37-3.18-4.25-36.85-1.41-48.2 3.34-13.35 10.2-19.58 22.93-20.81 14.04-1.32 17.83 17.75 17.86 17.94l.02 49.02-16.12 56.43-36.75 74.97z" fill="#6c63ff"/><path d="M440.94 58.87c-4.3.56-7.54-3.83-9.04-7.9s-2.64-8.78-6.38-10.98c-5.1-3-11.62.61-17.45-.38-6.59-1.11-10.87-8.1-11.2-14.76s2.31-13.1 4.92-19.24l.9 7.64A15.16 15.16 0 01409.33 0l-1.17 11.22c.73-6.29 7.5-11.16 13.7-9.85l-.19 6.68c7.6-.9 15.28-1.81 22.91-1.12s15.31 3.1 21.1 8.13c8.64 7.51 11.8 19.89 10.74 31.3s-5.77 22.13-10.68 32.48c-1.23 2.6-2.94 5.54-5.8 5.88-2.58.3-4.93-1.86-5.73-4.32s-.41-5.14.07-7.69c.72-3.84 1.63-7.77.95-11.63s-3.45-7.66-7.33-8.13-7.86 3.97-6 7.4z" fill="#2f2e41"/><path fill="#3f3d56" d="M597.73 535.1H339.99v-2.11h258.12l-.38 2.1z"/></svg> | ||||||
| After Width: | Height: | Size: 3.1 KiB | 
							
								
								
									
										404
									
								
								src/components/donors/AddDonorModal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,404 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { clickOutside } from "../base/outsideclick"; | ||||||
|  |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|  |   import { | ||||||
|  |     DonorService | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import isEmail from "validator/es/lib/isEmail"; | ||||||
|  |   import isMobilePhone from "validator/es/lib/isMobilePhone"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   export let modal_open; | ||||||
|  |   export let current_donors; | ||||||
|  |   let firstname_input; | ||||||
|  |   let lastname_input; | ||||||
|  |   let middlename_input; | ||||||
|  |   let phone_input; | ||||||
|  |   let email_input; | ||||||
|  |   let address_input1; | ||||||
|  |   let address_input2; | ||||||
|  |   let address_zipcode; | ||||||
|  |   let address_city; | ||||||
|  |   function focus(el) { | ||||||
|  |     el.focus(); | ||||||
|  |   } | ||||||
|  |   $: middlename_input_value = ""; | ||||||
|  |   $: phone_input_value = ""; | ||||||
|  |   $: email_input_value = ""; | ||||||
|  |   $: lastname_input_value = ""; | ||||||
|  |   $: firstname_input_value = ""; | ||||||
|  |   $: address_input1_value = ""; | ||||||
|  |   $: address_input2_value = ""; | ||||||
|  |   $: address_zipcode_value = ""; | ||||||
|  |   $: address_city_value = ""; | ||||||
|  |   $: processed_last_submit = true; | ||||||
|  |   $: address_checked = false; | ||||||
|  |   $: isPhoneValidOrEmpty = | ||||||
|  |     (phone_input_value.includes("+") && | ||||||
|  |       isMobilePhone( | ||||||
|  |         phone_input_value | ||||||
|  |           .replaceAll("(", "") | ||||||
|  |           .replaceAll(")", "") | ||||||
|  |           .replaceAll("-", "") | ||||||
|  |           .replaceAll(" ", "") | ||||||
|  |       )) || | ||||||
|  |     phone_input_value === ""; | ||||||
|  |   $: isEmailValidOrEmpty = | ||||||
|  |     isEmail(email_input_value) || email_input_value === ""; | ||||||
|  |   $: isLastnameValid = lastname_input_value.trim().length !== 0; | ||||||
|  |   $: isFirstnameValid = firstname_input_value.trim().length !== 0; | ||||||
|  |   $: isAddress1Valid = address_input1_value.trim().length !== 0; | ||||||
|  |   $: iszipcodevalid = address_zipcode_value.trim().length !== 0; | ||||||
|  |   $: iscityvalid = address_city_value.trim().length !== 0; | ||||||
|  |   $: createbtnenabled = | ||||||
|  |     isFirstnameValid && | ||||||
|  |     isLastnameValid && | ||||||
|  |     isEmailValidOrEmpty && | ||||||
|  |     isPhoneValidOrEmpty && | ||||||
|  |     ((isAddress1Valid && iszipcodevalid && iscityvalid) || | ||||||
|  |       address_checked === false); | ||||||
|  |   (() => { | ||||||
|  |     document.onkeydown = (e) => { | ||||||
|  |       e = e || window.event; | ||||||
|  |       if (e.key === "Escape") { | ||||||
|  |         modal_open = false; | ||||||
|  |       } | ||||||
|  |       if (e.keyCode === 13) { | ||||||
|  |         if (createbtnenabled === true) { | ||||||
|  |           createbtnenabled = false; | ||||||
|  |           submit(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   })(); | ||||||
|  |   function submit() { | ||||||
|  |     if (processed_last_submit === true) { | ||||||
|  |       processed_last_submit = false; | ||||||
|  |       const toast = Toastify({ | ||||||
|  |         text: $_('donor-is-being-added'), | ||||||
|  |         duration: -1, | ||||||
|  |       }).showToast(); | ||||||
|  |       let address = {}; | ||||||
|  |       if (address_checked === true) { | ||||||
|  |         address = { | ||||||
|  |           address1: address_input1_value, | ||||||
|  |           address2: address_input2_value || "", | ||||||
|  |           postalcode: address_zipcode_value, | ||||||
|  |           city: address_city_value, | ||||||
|  |           country: "DE", | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |       let postdata = { | ||||||
|  |         firstname: firstname_input_value, | ||||||
|  |         lastname: lastname_input_value, | ||||||
|  |         address, | ||||||
|  |         receiptNeeded: address_checked | ||||||
|  |       }; | ||||||
|  |       if (middlename_input_value) { | ||||||
|  |         postdata.middlename = middlename_input_value; | ||||||
|  |       } | ||||||
|  |       if (phone_input_value) { | ||||||
|  |         postdata.phone = phone_input_value; | ||||||
|  |       } | ||||||
|  |       if (email_input_value) { | ||||||
|  |         postdata.email = email_input_value; | ||||||
|  |       } | ||||||
|  |       DonorService.donorControllerPost(postdata) | ||||||
|  |         .then((result) => { | ||||||
|  |           firstname_input_value = ""; | ||||||
|  |           lastname_input_value = ""; | ||||||
|  |           middlename_input_value = ""; | ||||||
|  |           email_input_value = ""; | ||||||
|  |           modal_open = false; | ||||||
|  |           // | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_('donor-added'), | ||||||
|  |             duration: 500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |           current_donors.push(result); | ||||||
|  |           current_donors = current_donors; | ||||||
|  |         }) | ||||||
|  |         .catch((err) => { | ||||||
|  |           // | ||||||
|  |         }) | ||||||
|  |         .finally(() => { | ||||||
|  |           processed_last_submit = true; | ||||||
|  |           // | ||||||
|  |           toast.hideToast(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if modal_open} | ||||||
|  |   <div | ||||||
|  |     class="fixed z-10 inset-0 overflow-y-auto" | ||||||
|  |     use:focusTrap | ||||||
|  |     use:clickOutside | ||||||
|  |     on:click_outside={() => { | ||||||
|  |       modal_open = false; | ||||||
|  |     }}> | ||||||
|  |     <div | ||||||
|  |       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||||
|  |       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||||
|  |         <div | ||||||
|  |           class="absolute inset-0 bg-gray-500 opacity-75" | ||||||
|  |           data-id="modal_backdrop" /> | ||||||
|  |       </div> | ||||||
|  |       <span | ||||||
|  |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|  |         aria-hidden="true">​</span> | ||||||
|  |       <div | ||||||
|  |         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|  |         role="dialog" | ||||||
|  |         aria-modal="true" | ||||||
|  |         aria-labelledby="modal-headline"> | ||||||
|  |         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||||
|  |           <div class="sm:flex sm:items-start"> | ||||||
|  |             <div | ||||||
|  |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|  |               <svg | ||||||
|  |                 class="h-6 w-6 text-blue-600" | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |         d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|  |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |                 {$_('create-a-new-donor')} | ||||||
|  |               </h3> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <p class="text-sm text-gray-500"> | ||||||
|  |                   {$_('please-provide-the-nessecary-information-to-add-a-new-donor')} | ||||||
|  |                 </p> | ||||||
|  |               </div> | ||||||
|  |               <div class="grid grid-cols-6 gap-6"> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="firstname" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('first-name')}</label> | ||||||
|  |                   <input | ||||||
|  |                     use:focus | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('first-name')} | ||||||
|  |                     class:border-red-500={!isFirstnameValid} | ||||||
|  |                     class:focus:border-red-500={!isFirstnameValid} | ||||||
|  |                     class:focus:ring-red-500={!isFirstnameValid} | ||||||
|  |                     bind:value={firstname_input_value} | ||||||
|  |                     bind:this={firstname_input} | ||||||
|  |                     type="text" | ||||||
|  |                     name="firstname" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !isFirstnameValid} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('first-name-is-required')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="trackname" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('middle-name')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('middle-name')} | ||||||
|  |                     bind:value={middlename_input_value} | ||||||
|  |                     bind:this={middlename_input} | ||||||
|  |                     type="text" | ||||||
|  |                     name="trackname" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="lastname" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('last-name')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder="{$_('last-name')}" | ||||||
|  |                     class:border-red-500={!isLastnameValid} | ||||||
|  |                     class:focus:border-red-500={!isLastnameValid} | ||||||
|  |                     class:focus:ring-red-500={!isLastnameValid} | ||||||
|  |                     bind:value={lastname_input_value} | ||||||
|  |                     bind:this={lastname_input} | ||||||
|  |                     type="text" | ||||||
|  |                     name="lastname" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !isLastnameValid} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('last-name-is-required')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="phone" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('phone')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('phone')} | ||||||
|  |                     class:border-red-500={!isPhoneValidOrEmpty} | ||||||
|  |                     class:focus:border-red-500={!isPhoneValidOrEmpty} | ||||||
|  |                     class:focus:ring-red-500={!isPhoneValidOrEmpty} | ||||||
|  |                     bind:value={phone_input_value} | ||||||
|  |                     bind:this={phone_input} | ||||||
|  |                     type="tel" | ||||||
|  |                     name="phone" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !isPhoneValidOrEmpty} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="email" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('e-mail-adress')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('e-mail-adress')} | ||||||
|  |                     class:border-red-500={!isEmailValidOrEmpty} | ||||||
|  |                     class:focus:border-red-500={!isEmailValidOrEmpty} | ||||||
|  |                     class:focus:ring-red-500={!isEmailValidOrEmpty} | ||||||
|  |                     bind:value={email_input_value} | ||||||
|  |                     bind:this={email_input} | ||||||
|  |                     type="email" | ||||||
|  |                     name="email" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !isEmailValidOrEmpty} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('valid-email-is-required')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="flex items-start"> | ||||||
|  |                   <div class="flex items-center h-5"> | ||||||
|  |                     <input | ||||||
|  |                       bind:checked={address_checked} | ||||||
|  |                       id="comments" | ||||||
|  |                       name="comments" | ||||||
|  |                       type="checkbox" | ||||||
|  |                       class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |                   </div> | ||||||
|  |                   <div class="ml-3 text-sm"> | ||||||
|  |                     <label | ||||||
|  |                       for="comments" | ||||||
|  |                       class="font-medium text-gray-700">{$_('receipt-needed')}</label> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |                 {#if address_checked === true} | ||||||
|  |                   <div class="col-span-6"> | ||||||
|  |                     <label | ||||||
|  |                       for="address1" | ||||||
|  |                       class="block text-sm font-medium text-gray-700">{$_('address')}</label> | ||||||
|  |                     <input | ||||||
|  |                       autocomplete="off" | ||||||
|  |                       placeholder="Address" | ||||||
|  |                       class:border-red-500={!isAddress1Valid} | ||||||
|  |                       class:focus:border-red-500={!isAddress1Valid} | ||||||
|  |                       class:focus:ring-red-500={!isAddress1Valid} | ||||||
|  |                       bind:value={address_input1_value} | ||||||
|  |                       bind:this={address_input1} | ||||||
|  |                       type="text" | ||||||
|  |                       name="address1" | ||||||
|  |                       class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                     {#if !isAddress1Valid} | ||||||
|  |                       <span | ||||||
|  |                         class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                         {$_('address-is-required')} | ||||||
|  |                       </span> | ||||||
|  |                     {/if} | ||||||
|  |                   </div> | ||||||
|  |                   <div class="col-span-6"> | ||||||
|  |                     <label | ||||||
|  |                       for="address2" | ||||||
|  |                       class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label> | ||||||
|  |                     <input | ||||||
|  |                       autocomplete="off" | ||||||
|  |                       placeholder={$_('apartment-suite-etc')} | ||||||
|  |                       bind:value={address_input2_value} | ||||||
|  |                       bind:this={address_input2} | ||||||
|  |                       type="text" | ||||||
|  |                       name="address2" | ||||||
|  |                       class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   </div> | ||||||
|  |                   <div class="col-span-6"> | ||||||
|  |                     <label | ||||||
|  |                       for="zipcode" | ||||||
|  |                       class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label> | ||||||
|  |                     <input | ||||||
|  |                       autocomplete="off" | ||||||
|  |                       placeholder={$_('zip-postal-code')} | ||||||
|  |                       class:border-red-500={!iszipcodevalid} | ||||||
|  |                       class:focus:border-red-500={!iszipcodevalid} | ||||||
|  |                       class:focus:ring-red-500={!iszipcodevalid} | ||||||
|  |                       bind:value={address_zipcode_value} | ||||||
|  |                       bind:this={address_zipcode} | ||||||
|  |                       type="text" | ||||||
|  |                       name="zipcode" | ||||||
|  |                       class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                     {#if !iszipcodevalid} | ||||||
|  |                       <span | ||||||
|  |                         class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                         {$_('valid-zipcode-postal-code-is-required')} | ||||||
|  |                       </span> | ||||||
|  |                     {/if} | ||||||
|  |                   </div> | ||||||
|  |                   <div class="col-span-6"> | ||||||
|  |                     <label | ||||||
|  |                       for="city" | ||||||
|  |                       class="block text-sm font-medium text-gray-700">City</label> | ||||||
|  |                     <input | ||||||
|  |                       autocomplete="off" | ||||||
|  |                       placeholder="City" | ||||||
|  |                       class:border-red-500={!iscityvalid} | ||||||
|  |                       class:focus:border-red-500={!iscityvalid} | ||||||
|  |                       class:focus:ring-red-500={!iscityvalid} | ||||||
|  |                       bind:value={address_city_value} | ||||||
|  |                       bind:this={address_city} | ||||||
|  |                       type="text" | ||||||
|  |                       name="city" | ||||||
|  |                       class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                     {#if !iscityvalid} | ||||||
|  |                       <span | ||||||
|  |                         class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                         {$_('valid-city-is-required')} | ||||||
|  |                       </span> | ||||||
|  |                     {/if} | ||||||
|  |                   </div> | ||||||
|  |                 {/if} | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||||
|  |           <button | ||||||
|  |             disabled={!createbtnenabled} | ||||||
|  |             class:opacity-50={!createbtnenabled} | ||||||
|  |             on:click={submit} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('create')} | ||||||
|  |           </button> | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |               modal_open = false; | ||||||
|  |             }} | ||||||
|  |             type="button" | ||||||
|  |             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('cancel')} | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
							
								
								
									
										92
									
								
								src/components/donors/ConfirmDonorDeletion.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,92 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { clickOutside } from "../base/outsideclick"; | ||||||
|  |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|  |   import { DonorService } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import { createEventDispatcher } from "svelte"; | ||||||
|  |   export let modal_open; | ||||||
|  |   export let delete_donor; | ||||||
|  |   const dispatch = createEventDispatcher(); | ||||||
|  |   function cancelDelete() { | ||||||
|  |     modal_open = false; | ||||||
|  |     dispatch("cancelDelete", { id: delete_donor.id }); | ||||||
|  |   } | ||||||
|  |   function deleteDonor() { | ||||||
|  |     DonorService.donorControllerRemove( | ||||||
|  |       delete_donor.id, | ||||||
|  |       true | ||||||
|  |     ) | ||||||
|  |       .then((resp) => { | ||||||
|  |         Toastify({ | ||||||
|  |           text: "Donor deleted", | ||||||
|  |           duration: 500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |         location.replace("./"); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => {}); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if modal_open} | ||||||
|  |   <div | ||||||
|  |     class="fixed z-10 inset-0 overflow-y-auto" | ||||||
|  |     use:focusTrap | ||||||
|  |     use:clickOutside | ||||||
|  |     on:click_outside={cancelDelete}> | ||||||
|  |     <div | ||||||
|  |       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||||
|  |       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||||
|  |         <div | ||||||
|  |           class="absolute inset-0 bg-gray-500 opacity-75" | ||||||
|  |           data-id="modal_backdrop" /> | ||||||
|  |       </div> | ||||||
|  |       <span | ||||||
|  |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|  |         aria-hidden="true">​</span> | ||||||
|  |       <div | ||||||
|  |         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|  |         role="dialog" | ||||||
|  |         aria-modal="true" | ||||||
|  |         aria-labelledby="modal-headline"> | ||||||
|  |         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||||
|  |           <div class="sm:flex sm:items-start"> | ||||||
|  |             <div | ||||||
|  |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|  |               <svg class="h-6 w-6 text-blue-600" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M9.33 11.5h2.17A4.5 4.5 0 0116 16H9v1h8v-1a5.58 5.58 0 00-.89-3H19a5 5 0 014.52 2.85A13.15 13.15 0 0113 21c-2.76 0-5.1-.59-7-1.63v-9.3a6.97 6.97 0 013.33 1.43zM5 19a1 1 0 01-1 1H2a1 1 0 01-1-1v-9a1 1 0 011-1h2a1 1 0 011 1v9zM18 5a3 3 0 110 6 3 3 0 010-6zm-7-3a3 3 0 110 6 3 3 0 010-6z"/></svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|  |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |                 {$_('attention')} | ||||||
|  |               </h3> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <p class="text-sm text-gray-500"> | ||||||
|  |                   {$_( | ||||||
|  |                     'do-you-want-to-delete-this-donor-with-all-related-donations' | ||||||
|  |                   )} | ||||||
|  |                   <br />  | ||||||
|  |                   {$_('all-associated-donations-will-get-deleted-as-well')} | ||||||
|  |                 </p> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||||
|  |           <button | ||||||
|  |             on:click={deleteDonor} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('confirm-delete-donor-with-all-donations')} | ||||||
|  |           </button> | ||||||
|  |           <button | ||||||
|  |             on:click={cancelDelete} | ||||||
|  |             type="button" | ||||||
|  |             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('cancel-keep-donor')} | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
							
								
								
									
										406
									
								
								src/components/donors/DonorDetail.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,406 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import { DonorService, DonationService } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import PromiseError from "../base/PromiseError.svelte"; | ||||||
|  |   import isEmail from "validator/es/lib/isEmail"; | ||||||
|  |   import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte"; | ||||||
|  |   let data_loaded = false; | ||||||
|  |   export let params; | ||||||
|  |   $: delete_triggered = false; | ||||||
|  |   $: original_data = {}; | ||||||
|  |   $: editable = {}; | ||||||
|  |   $: current_donations = []; | ||||||
|  |   $: changes_performed = !( | ||||||
|  |     JSON.stringify(original_data) === JSON.stringify(editable) | ||||||
|  |   ); | ||||||
|  |   $: isEmailValid = | ||||||
|  |     (editable.email || "") === "" || | ||||||
|  |     (editable.email && isEmail(editable.email || "")); | ||||||
|  |   $: isFirstnameValid = editable.firstname !== ""; | ||||||
|  |   $: isLastnameValid = editable.lastname !== ""; | ||||||
|  |   $: save_enabled = | ||||||
|  |     changes_performed && | ||||||
|  |     isFirstnameValid && | ||||||
|  |     isLastnameValid && | ||||||
|  |     isEmailValid && | ||||||
|  |     isPhoneValidOrEmpty && | ||||||
|  |     ((isAddress1Valid && iszipcodevalid && iscityvalid) || | ||||||
|  |       editable.address_checked === false); | ||||||
|  |   const donation_promise = DonationService.donationControllerGetAll().then( | ||||||
|  |     (val) => { | ||||||
|  |       current_donations = val; | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   const promise = DonorService.donorControllerGetOne(params.donorid).then( | ||||||
|  |     (data) => { | ||||||
|  |       data_loaded = true; | ||||||
|  |       original_data = Object.assign(original_data, data); | ||||||
|  |       editable = Object.assign(editable, original_data); | ||||||
|  |       editable.address_checked = editable.address.address1 !== null; | ||||||
|  |       original_data.address_checked = editable.address.address1 !== null; | ||||||
|  |       if (editable.address_checked === false) { | ||||||
|  |         editable.address = { | ||||||
|  |           address1: "", | ||||||
|  |           address2: "", | ||||||
|  |           city: "", | ||||||
|  |           postalcode: "", | ||||||
|  |           country: "", | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   $: isPhoneValidOrEmpty = | ||||||
|  |     editable.phone?.includes("+") || | ||||||
|  |     editable.phone === "" || | ||||||
|  |     editable.phone === null; | ||||||
|  |   $: isAddress1Valid = editable.address?.address1?.trim().length !== 0; | ||||||
|  |   $: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0; | ||||||
|  |   $: iscityvalid = editable.address?.city?.trim().length !== 0; | ||||||
|  |   let modal_open = false; | ||||||
|  |   let delete_donor = {}; | ||||||
|  |   function submit() { | ||||||
|  |     if (data_loaded === true && save_enabled) { | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_("donor-is-being-updated"), | ||||||
|  |         duration: 2500, | ||||||
|  |       }).showToast(); | ||||||
|  |       editable.address.country = "DE"; | ||||||
|  |       if (editable.address_checked === false) { | ||||||
|  |         editable.address = null; | ||||||
|  |       } | ||||||
|  |       if (editable.email) editable.email = editable.email; | ||||||
|  |       if (editable.phone) editable.phone = editable.phone; | ||||||
|  |       if (editable.middlename) editable.middlename = editable.middlename; | ||||||
|  |       editable.receiptNeeded = editable.address_checked; | ||||||
|  |       DonorService.donorControllerPut(original_data.id, editable) | ||||||
|  |         .then((resp) => { | ||||||
|  |           Object.assign(original_data, editable); | ||||||
|  |           original_data = original_data; | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_("updated-donor"), | ||||||
|  |             duration: 2500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |         }) | ||||||
|  |         .catch((err) => {}); | ||||||
|  |     } else { | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   function deleteDonor() { | ||||||
|  |     DonorService.donorControllerRemove(original_data.id, false) | ||||||
|  |       .then((resp) => { | ||||||
|  |         Toastify({ | ||||||
|  |           text: $_("donor-deleted"), | ||||||
|  |           duration: 500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |         location.replace("./"); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => { | ||||||
|  |         modal_open = true; | ||||||
|  |         delete_donor = original_data; | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <ConfirmDonorDeletion bind:modal_open bind:delete_donor /> | ||||||
|  | {#await promise && donation_promise} | ||||||
|  |   {$_('loading-donor-details')} | ||||||
|  | {:then} | ||||||
|  |   <section class="container p-5 select-none"> | ||||||
|  |     <div class="flex flex-row mb-4"> | ||||||
|  |       <div class="w-full"> | ||||||
|  |         <nav class="w-full flex"> | ||||||
|  |           <ol class="list-none flex flex-row items-center justify-start"> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <svg | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center ml-2"> | ||||||
|  |               <a class="mr-2" href="./">{$_('donors')}</a><svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 mr-2 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><line | ||||||
|  |                   x1="5" | ||||||
|  |                   y1="12" | ||||||
|  |                   x2="19" | ||||||
|  |                   y2="12" /> | ||||||
|  |                 <polyline points="12 5 19 12 12 19" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <span class="mr-2">{original_data.firstname} | ||||||
|  |                 {original_data.middlename || ''} | ||||||
|  |                 {original_data.lastname}</span> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </nav> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="mb-8 text-3xl font-extrabold leading-tight"> | ||||||
|  |       {original_data.firstname} | ||||||
|  |       {original_data.middlename || ''} | ||||||
|  |       {original_data.lastname} | ||||||
|  |       <span data-id="donor_actions_${editable.id}"> | ||||||
|  |         {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:DELETE')} | ||||||
|  |           {#if delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={deleteDonor} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:">{$_('confirm-deletion')}</button> | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = !delete_triggered; | ||||||
|  |               }} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:">{$_('cancel')}</button> | ||||||
|  |           {/if} | ||||||
|  |           {#if !delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = true; | ||||||
|  |               }} | ||||||
|  |               type="button" | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:">{$_('delete-donor')}</button> | ||||||
|  |           {/if} | ||||||
|  |         {/if} | ||||||
|  |         {#if !delete_triggered} | ||||||
|  |           <button | ||||||
|  |             disabled={!save_enabled} | ||||||
|  |             class:opacity-50={!save_enabled} | ||||||
|  |             type="button" | ||||||
|  |             on:click={submit} | ||||||
|  |             class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:">{$_('save-changes')}</button> | ||||||
|  |         {/if} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |     <!--  --> | ||||||
|  |     <div> | ||||||
|  |       <span | ||||||
|  |         class="font-medium text-gray-700">{$_('total-donation-amount')}:</span> | ||||||
|  |       <span>{(editable.donationAmount / 100) | ||||||
|  |           .toFixed(2) | ||||||
|  |           .toLocaleString('de-DE', { valute: 'EUR' })}€</span> | ||||||
|  |       <br /> | ||||||
|  |       <span class="font-medium text-gray-700">{$_('donations')}:</span> | ||||||
|  |       {#if current_donations.filter((d) => d.donor.id == editable.id).length > 0} | ||||||
|  |         {#each current_donations.filter((o) => o.donor.id == editable.id) as d} | ||||||
|  |           {#if d.responseType === 'DISTANCEDONATION'} | ||||||
|  |             <a | ||||||
|  |               href="../donations/{d.id}" | ||||||
|  |               class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname} | ||||||
|  |               {d.runner.middlename} | ||||||
|  |               {d.runner.lastname}</a> | ||||||
|  |           {:else} | ||||||
|  |             <a | ||||||
|  |               href="../donations/{d.id}" | ||||||
|  |               class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1">{$_('fixed-donation')}: | ||||||
|  |               {(d.amount / 100) | ||||||
|  |                 .toFixed(2) | ||||||
|  |                 .toLocaleString('de-DE', { valute: 'EUR' })}€</a> | ||||||
|  |           {/if} | ||||||
|  |         {/each} | ||||||
|  |       {:else}{$_('donor-has-no-associated-donations')}{/if} | ||||||
|  |     </div> | ||||||
|  |     <div class=" w-full"> | ||||||
|  |       <label | ||||||
|  |         for="firstname" | ||||||
|  |         class="font-medium text-gray-700">{$_('first-name')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('first-name')} | ||||||
|  |         type="text" | ||||||
|  |         class:border-red-500={!isFirstnameValid} | ||||||
|  |         class:focus:border-red-500={!isFirstnameValid} | ||||||
|  |         class:focus:ring-red-500={!isFirstnameValid} | ||||||
|  |         bind:value={editable.firstname} | ||||||
|  |         name="firstname" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       {#if !isFirstnameValid} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('first-name-is-required')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |     <div class=" w-full"> | ||||||
|  |       <label | ||||||
|  |         for="middlename" | ||||||
|  |         class="font-medium text-gray-700">{$_('middle-name')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('middle-name')} | ||||||
|  |         type="text" | ||||||
|  |         bind:value={editable.middlename} | ||||||
|  |         name="middlename" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |     </div> | ||||||
|  |     <div class=" w-full"> | ||||||
|  |       <label | ||||||
|  |         for="lastname" | ||||||
|  |         class="font-medium text-gray-700">{$_('last-name')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('last-name')} | ||||||
|  |         type="text" | ||||||
|  |         bind:value={editable.lastname} | ||||||
|  |         class:border-red-500={!isLastnameValid} | ||||||
|  |         class:focus:border-red-500={!isLastnameValid} | ||||||
|  |         class:focus:ring-red-500={!isLastnameValid} | ||||||
|  |         name="lastname" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       {#if !isLastnameValid} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('last-name-is-required')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |     <div class=" w-full"> | ||||||
|  |       <label | ||||||
|  |         for="email" | ||||||
|  |         class="font-medium text-gray-700">{$_('e-mail-adress')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('e-mail-adress')} | ||||||
|  |         type="email" | ||||||
|  |         bind:value={editable.email} | ||||||
|  |         class:border-red-500={!isEmailValid} | ||||||
|  |         class:focus:border-red-500={!isEmailValid} | ||||||
|  |         class:focus:ring-red-500={!isEmailValid} | ||||||
|  |         name="email" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       {#if !isEmailValid} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('valid-email-is-required')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |     <div class=" w-full"> | ||||||
|  |       <label for="phone" class="font-medium text-gray-700">{$_('phone')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('phone')} | ||||||
|  |         type="tel" | ||||||
|  |         class:border-red-500={!isPhoneValidOrEmpty} | ||||||
|  |         class:focus:border-red-500={!isPhoneValidOrEmpty} | ||||||
|  |         class:focus:ring-red-500={!isPhoneValidOrEmpty} | ||||||
|  |         bind:value={editable.phone} | ||||||
|  |         name="phone" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       {#if !isPhoneValidOrEmpty} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('valid-international-phone-number-is-required')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |     <div class="flex items-start mt-2"> | ||||||
|  |       <div class="flex items-center h-5"> | ||||||
|  |         <input | ||||||
|  |           bind:checked={editable.address_checked} | ||||||
|  |           id="comments" | ||||||
|  |           name="comments" | ||||||
|  |           type="checkbox" | ||||||
|  |           class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |       </div> | ||||||
|  |       <div class="ml-3 "> | ||||||
|  |         <label | ||||||
|  |           for="comments" | ||||||
|  |           class="font-medium text-gray-700">{$_('receipt-needed')}</label> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     {#if editable.address_checked === true} | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="address1" | ||||||
|  |           class="block  font-medium text-gray-700">{$_('address')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder="Address" | ||||||
|  |           class:border-red-500={!isAddress1Valid} | ||||||
|  |           class:focus:border-red-500={!isAddress1Valid} | ||||||
|  |           class:focus:ring-red-500={!isAddress1Valid} | ||||||
|  |           bind:value={editable.address.address1} | ||||||
|  |           type="text" | ||||||
|  |           name="address1" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |         {#if !isAddress1Valid} | ||||||
|  |           <span | ||||||
|  |             class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |             {$_('address-is-required')} | ||||||
|  |           </span> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="address2" | ||||||
|  |           class="block  font-medium text-gray-700">{$_('apartment-suite-etc')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder={$_('apartment-suite-etc')} | ||||||
|  |           bind:value={editable.address.address2} | ||||||
|  |           type="text" | ||||||
|  |           name="address2" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       </div> | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="zipcode" | ||||||
|  |           class="block  font-medium text-gray-700">{$_('zip-postal-code')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder={$_('zip-postal-code')} | ||||||
|  |           class:border-red-500={!iszipcodevalid} | ||||||
|  |           class:focus:border-red-500={!iszipcodevalid} | ||||||
|  |           class:focus:ring-red-500={!iszipcodevalid} | ||||||
|  |           bind:value={editable.address.postalcode} | ||||||
|  |           type="text" | ||||||
|  |           name="zipcode" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |         {#if !iszipcodevalid} | ||||||
|  |           <span | ||||||
|  |             class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |             {$_('valid-zipcode-postal-code-is-required')} | ||||||
|  |           </span> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="city" | ||||||
|  |           class="block  font-medium text-gray-700">{$_('city')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder={$_('city')} | ||||||
|  |           class:border-red-500={!iscityvalid} | ||||||
|  |           class:focus:border-red-500={!iscityvalid} | ||||||
|  |           class:focus:ring-red-500={!iscityvalid} | ||||||
|  |           bind:value={editable.address.city} | ||||||
|  |           type="text" | ||||||
|  |           name="city" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |         {#if !iscityvalid} | ||||||
|  |           <span | ||||||
|  |             class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |             {$_('valid-city-is-required')} | ||||||
|  |           </span> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |   </section> | ||||||
|  | {:catch error} | ||||||
|  |   <PromiseError {error} /> | ||||||
|  | {/await} | ||||||
							
								
								
									
										29
									
								
								src/components/donors/Donors.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import AddDonorModal from "./AddDonorModal.svelte"; | ||||||
|  |   import DonorsOverview from "./DonorsOverview.svelte"; | ||||||
|  |   $: current_donors = []; | ||||||
|  |   export let modal_open = false; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <section class="container p-5"> | ||||||
|  |   <span class="mb-1 text-3xl font-extrabold leading-tight"> | ||||||
|  |     {$_('donors')} | ||||||
|  |     {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:CREATE')} | ||||||
|  |       <button | ||||||
|  |         on:click={() => { | ||||||
|  |           modal_open = true; | ||||||
|  |         }} | ||||||
|  |         type="button" | ||||||
|  |         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |         {$_('add-donor')} | ||||||
|  |       </button> | ||||||
|  |     {/if} | ||||||
|  |   </span> | ||||||
|  |   <DonorsOverview bind:current_donors /> | ||||||
|  | </section> | ||||||
|  |  | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:CREATE')} | ||||||
|  |   <AddDonorModal bind:current_donors bind:modal_open /> | ||||||
|  | {/if} | ||||||
							
								
								
									
										12
									
								
								src/components/donors/DonorsEmptyState.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import donors_empty from "./donors_empty.svg"; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div class="text-center items-center justify-center"> | ||||||
|  |   <p class="mb-16 text-lg text-gray-500"> | ||||||
|  |     <img class="w-full" style="height:15rem" src={donors_empty} alt="" /> | ||||||
|  |     <span class="font-bold">{$_('there-are-no-donors-yet')}</span><br /> | ||||||
|  |     <span>{$_('add-your-first-donor')}</span> | ||||||
|  |   </p> | ||||||
|  | </div> | ||||||
							
								
								
									
										212
									
								
								src/components/donors/DonorsOverview.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,212 @@ | |||||||
|  | <script> | ||||||
|  |   import { getLocaleFromNavigator, _ } from "svelte-i18n"; | ||||||
|  |   import { DonationService, DonorService } from "@odit/lfk-client-js"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import DonorsEmptyState from "./DonorsEmptyState.svelte"; | ||||||
|  |   import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   $: searchvalue = ""; | ||||||
|  |   $: active_deletes = []; | ||||||
|  |   $: current_donations = []; | ||||||
|  |   let modal_open = false; | ||||||
|  |   let delete_donor = {}; | ||||||
|  |   export let current_donors = []; | ||||||
|  |   const donors_promise = DonorService.donorControllerGetAll().then((val) => { | ||||||
|  |     current_donors = val; | ||||||
|  |   }); | ||||||
|  |   const donation_promise = DonationService.donationControllerGetAll().then( | ||||||
|  |     (val) => { | ||||||
|  |       current_donations = val; | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   function should_display_based_on_id(id) { | ||||||
|  |     if (searchvalue.toString().slice(-1) === "*") { | ||||||
|  |       return id.toString().startsWith(searchvalue.replace("*", "")); | ||||||
|  |     } | ||||||
|  |     return id.toString() === searchvalue; | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <ConfirmDonorDeletion | ||||||
|  |   on:cancelDelete={(event) => { | ||||||
|  |     modal_open = false; | ||||||
|  |     active_deletes[event.detail.id] = false; | ||||||
|  |   }} | ||||||
|  |   bind:modal_open | ||||||
|  |   bind:delete_donor /> | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')} | ||||||
|  |   {#await donors_promise && donation_promise} | ||||||
|  |     <div | ||||||
|  |       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||||
|  |       role="alert"> | ||||||
|  |       <p class="font-bold">{$_('donors-are-being-loaded')}</p> | ||||||
|  |       <p class="text-sm">{$_('this-might-take-a-moment')}</p> | ||||||
|  |     </div> | ||||||
|  |   {:then} | ||||||
|  |     {#if current_donors.length === 0} | ||||||
|  |       <DonorsEmptyState /> | ||||||
|  |     {:else} | ||||||
|  |       <input | ||||||
|  |         type="search" | ||||||
|  |         bind:value={searchvalue} | ||||||
|  |         placeholder={$_('datatable.search')} | ||||||
|  |         aria-label={$_('datatable.search')} | ||||||
|  |         class="gridjs-input gridjs-search-input mb-4" /> | ||||||
|  |       <div | ||||||
|  |         class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> | ||||||
|  |         <table class="divide-y divide-gray-200 w-full"> | ||||||
|  |           <thead class="bg-gray-50"> | ||||||
|  |             <tr> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('name')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('contact-information')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('donations')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('total-donation-amount')} | ||||||
|  |               </th> | ||||||
|  |               <th scope="col" class="relative px-6 py-3"> | ||||||
|  |                 <span class="sr-only">{$_('action')}</span> | ||||||
|  |               </th> | ||||||
|  |             </tr> | ||||||
|  |           </thead> | ||||||
|  |           <tbody class="divide-y divide-gray-200"> | ||||||
|  |             {#each current_donors as donor} | ||||||
|  |               {#if donor.firstname | ||||||
|  |                 .toLowerCase() | ||||||
|  |                 .includes( | ||||||
|  |                   searchvalue.toLowerCase() | ||||||
|  |                 ) || donor.middlename | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || donor.lastname | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || should_display_based_on_id(donor.id)} | ||||||
|  |                 <tr data-rowid="donor_{donor.id}"> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <div class="flex items-center"> | ||||||
|  |                       <div class="ml-4"> | ||||||
|  |                         <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                           {donor.firstname} | ||||||
|  |                           {donor.middlename || ''} | ||||||
|  |                           {donor.lastname} | ||||||
|  |                         </div> | ||||||
|  |                       </div> | ||||||
|  |                     </div> | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     {#if donor.email} | ||||||
|  |                       <div class="text-sm text-gray-500">{donor.email}</div> | ||||||
|  |                     {/if} | ||||||
|  |                     {#if donor.phone} | ||||||
|  |                       <div class="text-sm text-gray-500">{donor.phone}</div> | ||||||
|  |                     {/if} | ||||||
|  |                     {#if donor.address.address1 !== null} | ||||||
|  |                       {donor.address.address1}<br /> | ||||||
|  |                       {donor.address.address2 || ''}<br /> | ||||||
|  |                       {donor.address.postalcode} | ||||||
|  |                       {donor.address.city} | ||||||
|  |                       {donor.address.country} | ||||||
|  |                     {/if} | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     {#if current_donations.filter((d) => d.donor.id == donor.id).length > 0} | ||||||
|  |                       {#each current_donations.filter((o) => o.donor.id == donor.id) as d} | ||||||
|  |                         {#if d.responseType === 'DISTANCEDONATION'} | ||||||
|  |                           <a | ||||||
|  |                             href="../donations/{d.id}" | ||||||
|  |                             class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname} | ||||||
|  |                             {d.runner.middlename} | ||||||
|  |                             {d.runner.lastname}</a> | ||||||
|  |                         {:else} | ||||||
|  |                           <a | ||||||
|  |                             href="../donations/{d.id}" | ||||||
|  |                             class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1">{$_('fixed-donation')}: | ||||||
|  |                             {(d.amount / 100) | ||||||
|  |                               .toFixed(2) | ||||||
|  |                               .toLocaleString('de-DE', { valute: 'EUR' })}€</a> | ||||||
|  |                         {/if} | ||||||
|  |                       {/each} | ||||||
|  |                     {:else}{$_('donor-has-no-associated-donations')}{/if} | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     {(donor.donationAmount / 100) | ||||||
|  |                       .toFixed(2) | ||||||
|  |                       .toLocaleString('de-DE', { valute: 'EUR' })}€ | ||||||
|  |                   </td> | ||||||
|  |                   {#if active_deletes[donor.id] === true} | ||||||
|  |                     <td | ||||||
|  |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                       <button | ||||||
|  |                         on:click={() => { | ||||||
|  |                           active_deletes[donor.id] = false; | ||||||
|  |                         }} | ||||||
|  |                         tabindex="0" | ||||||
|  |                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> | ||||||
|  |                       <button | ||||||
|  |                         on:click={() => { | ||||||
|  |                           DonorService.donorControllerRemove(donor.id, false) | ||||||
|  |                             .then((resp) => { | ||||||
|  |                               current_donors = current_donors.filter((obj) => obj.id !== donor.id); | ||||||
|  |                               Toastify({ | ||||||
|  |                                 text: 'Donor deleted', | ||||||
|  |                                 duration: 500, | ||||||
|  |                                 backgroundColor: | ||||||
|  |                                   'linear-gradient(to right, #00b09b, #96c93d)', | ||||||
|  |                               }).showToast(); | ||||||
|  |                             }) | ||||||
|  |                             .catch((err) => { | ||||||
|  |                               modal_open = true; | ||||||
|  |                               delete_donor = donor; | ||||||
|  |                             }); | ||||||
|  |                         }} | ||||||
|  |                         tabindex="0" | ||||||
|  |                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> | ||||||
|  |                     </td> | ||||||
|  |                   {:else} | ||||||
|  |                     <td | ||||||
|  |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                       <a | ||||||
|  |                         href="./{donor.id}" | ||||||
|  |                         class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> | ||||||
|  |                       {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:DELETE')} | ||||||
|  |                         <button | ||||||
|  |                           on:click={() => { | ||||||
|  |                             active_deletes[donor.id] = true; | ||||||
|  |                           }} | ||||||
|  |                           tabindex="0" | ||||||
|  |                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> | ||||||
|  |                       {/if} | ||||||
|  |                     </td> | ||||||
|  |                   {/if} | ||||||
|  |                 </tr> | ||||||
|  |               {/if} | ||||||
|  |             {/each} | ||||||
|  |           </tbody> | ||||||
|  |         </table> | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |   {:catch error} | ||||||
|  |     <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> | ||||||
|  |       <span class="inline-block align-middle mr-8"> | ||||||
|  |         <b class="capitalize">{$_('general_promise_error')}</b> | ||||||
|  |         {error} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |   {/await} | ||||||
|  | {/if} | ||||||
							
								
								
									
										1
									
								
								src/components/donors/donors_empty.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 40 KiB | 
| @@ -1,6 +1,6 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   import { clickOutside } from "./outsideclick"; |   import { clickOutside } from "../base/outsideclick"; | ||||||
|   import { focusTrap } from "svelte-focus-trap"; |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|   export let modal_open; |   export let modal_open; | ||||||
|   (function () { |   (function () { | ||||||
| @@ -189,34 +189,5 @@ | |||||||
|         </li> |         </li> | ||||||
|       </ul> |       </ul> | ||||||
|     </div> |     </div> | ||||||
|     <h2 class="mt-4 text-4xl font-display font-semibold md:text-5xl"> |  | ||||||
|       {$_('faq')} |  | ||||||
|     </h2> |  | ||||||
|     <div class="mt-6 border-t-2 border-gray-100 pt-10"> |  | ||||||
|       <dl class="md:grid md:grid-cols-2 md:gap-8"> |  | ||||||
|         <div> |  | ||||||
|           <div> |  | ||||||
|             <dt class="text-lg leading-6 font-medium">Q</dt> |  | ||||||
|             <dd class="mt-2"> |  | ||||||
|               <p class="text-base text-gray-500">A</p> |  | ||||||
|             </dd> |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="mt-12 sm:mt-0"> |  | ||||||
|           <div id="team-pricing"> |  | ||||||
|             <dt class="text-lg leading-6 font-medium">Q</dt> |  | ||||||
|             <dd class="mt-2"> |  | ||||||
|               <p class="text-base text-gray-500">A</p> |  | ||||||
|             </dd> |  | ||||||
|           </div> |  | ||||||
|           <div class="mt-12"> |  | ||||||
|             <dt class="text-lg leading-6 font-medium">Q</dt> |  | ||||||
|             <dd class="mt-2"> |  | ||||||
|               <p class="text-base text-gray-500">A</p> |  | ||||||
|             </dd> |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|       </dl> |  | ||||||
|     </div> |  | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
| @@ -32,6 +32,8 @@ | |||||||
|       target="_blank" |       target="_blank" | ||||||
|       rel="noopener, noreferrer" |       rel="noopener, noreferrer" | ||||||
|       href="https://git.odit.services/lfk/frontend/src/tag/{releaseinfo}">{releaseinfo}</a> |       href="https://git.odit.services/lfk/frontend/src/tag/{releaseinfo}">{releaseinfo}</a> | ||||||
|  |       - | ||||||
|  |     <a class="underline" href="https://docs.lauf-fuer-kaya.de" target="_blank">{$_('documentation')}</a> | ||||||
|     - |     - | ||||||
|     <a class="underline" href="/privacy">{$_('privacy')}</a> |     <a class="underline" href="/privacy">{$_('privacy')}</a> | ||||||
|     - |     - | ||||||
| @@ -1,11 +1,14 @@ | |||||||
| <script> | <script> | ||||||
|   import { _, getLocaleFromNavigator } from "svelte-i18n"; |   import { _, getLocaleFromNavigator } from "svelte-i18n"; | ||||||
|   import * as css from "./simple.css"; |  | ||||||
|   import marked from "marked"; |   import marked from "marked"; | ||||||
|   import Footer from "./Footer.svelte"; |   import Footer from "./Footer.svelte"; | ||||||
|  |   import * as css from "../base/simple.css"; | ||||||
|   let html = ""; |   let html = ""; | ||||||
|   async function load() { |   async function load() { | ||||||
|     let md = await fetch("/imprint_" + getLocaleFromNavigator() + ".md"); |     let md = await fetch("/imprint_" + getLocaleFromNavigator() + ".md"); | ||||||
|  |     if((await md.text()).includes("<meta")){ | ||||||
|  |       md.ok=false | ||||||
|  |     } | ||||||
|     if (!md.ok) { |     if (!md.ok) { | ||||||
|       md = await fetch("/imprint_en.md"); |       md = await fetch("/imprint_en.md"); | ||||||
|     } |     } | ||||||
| @@ -1,11 +1,14 @@ | |||||||
| <script> | <script> | ||||||
|   import { _, getLocaleFromNavigator } from "svelte-i18n"; |   import { _, getLocaleFromNavigator } from "svelte-i18n"; | ||||||
|   import * as css from "./simple.css"; |  | ||||||
|   import marked from "marked"; |   import marked from "marked"; | ||||||
|   import Footer from "./Footer.svelte"; |   import Footer from "./Footer.svelte"; | ||||||
|  |   import * as css from "../base/simple.css"; | ||||||
|   let html = ""; |   let html = ""; | ||||||
|   async function load() { |   async function load() { | ||||||
|     let md = await fetch("/privacy_" + getLocaleFromNavigator() + ".md"); |     let md = await fetch("/privacy_" + getLocaleFromNavigator() + ".md"); | ||||||
|  |     if((await md.text()).includes("<meta")){ | ||||||
|  |       md.ok=false | ||||||
|  |     } | ||||||
|     if (!md.ok) { |     if (!md.ok) { | ||||||
|       md = await fetch("/privacy_en.md"); |       md = await fetch("/privacy_en.md"); | ||||||
|     } |     } | ||||||
| @@ -1,19 +1,20 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   import { clickOutside } from "./outsideclick"; |   import { clickOutside } from "../base/outsideclick"; | ||||||
|   import { focusTrap } from "svelte-focus-trap"; |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|   import { RunnerOrganizationService } from "@odit/lfk-client-js"; |  | ||||||
|   import Toastify from "toastify-js"; |   import Toastify from "toastify-js"; | ||||||
|  |   import { UserGroupService } from "@odit/lfk-client-js"; | ||||||
|   export let modal_open; |   export let modal_open; | ||||||
|   export let current_organizations; |   export let current_groups; | ||||||
|   let name_input_dom; |   let description_input_value; | ||||||
|   function focus(el) { |   function focus(el) { | ||||||
|     el.focus(); |     el.focus(); | ||||||
|   } |   } | ||||||
|   $: name = ""; |   $: description_input_value = ""; | ||||||
|  |   $: name_input_value = ""; | ||||||
|   $: processed_last_submit = true; |   $: processed_last_submit = true; | ||||||
|   $: isOrgnameValid = name.trim().length !== 0; |   $: isNameValid = name_input_value.trim().length !== 0; | ||||||
|   $: createbtnenabled = isOrgnameValid; |   $: createbtnenabled = isNameValid; | ||||||
|   (() => { |   (() => { | ||||||
|     document.onkeydown = (e) => { |     document.onkeydown = (e) => { | ||||||
|       e = e || window.event; |       e = e || window.event; | ||||||
| @@ -32,25 +33,26 @@ | |||||||
|     if (processed_last_submit === true) { |     if (processed_last_submit === true) { | ||||||
|       processed_last_submit = false; |       processed_last_submit = false; | ||||||
|       const toast = Toastify({ |       const toast = Toastify({ | ||||||
|         text: "Organization is being added...", |         text: $_('group-is-being-added'), | ||||||
|         duration: -1, |         duration: -1, | ||||||
|       }).showToast(); |       }).showToast(); | ||||||
|       RunnerOrganizationService.runnerOrganizationControllerPost({ |       let postdata = { | ||||||
|         name, |         name: name_input_value, | ||||||
|         address: undefined, |         description: description_input_value, | ||||||
|         contact: undefined, |       }; | ||||||
|       }) |       UserGroupService.userGroupControllerPost(postdata) | ||||||
|         .then((result) => { |         .then((result) => { | ||||||
|           console.log(result); |           name_input_value = ""; | ||||||
|           name = ""; |           description_input_value = ""; | ||||||
|           modal_open = false; |           modal_open = false; | ||||||
|           // |           // | ||||||
|           Toastify({ |           Toastify({ | ||||||
|             text: "Organization added", |             text: $_('group-added'), | ||||||
|             duration: 500, |             duration: 500, | ||||||
|             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|           }).showToast(); |           }).showToast(); | ||||||
|           current_organizations = current_organizations.concat([result]); |           current_groups.push(result); | ||||||
|  |           current_groups = current_groups; | ||||||
|         }) |         }) | ||||||
|         .catch((err) => { |         .catch((err) => { | ||||||
|           // |           // | ||||||
| @@ -92,48 +94,59 @@ | |||||||
|             <div |             <div | ||||||
|               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|               <svg |               <svg | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 640 512" | ||||||
|                 class="h-6 w-6 text-blue-600" |                 class="h-6 w-6 text-blue-600" | ||||||
|                 fill="currentColor" |                 fill="currentColor" | ||||||
|                 xmlns="http://www.w3.org/2000/svg" |  | ||||||
|                 viewBox="0 0 24 24" |  | ||||||
|                 width="24" |                 width="24" | ||||||
|                 height="24"><path |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|                   d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg> |                 <path | ||||||
|  |                   d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> | ||||||
|             </div> |             </div> | ||||||
|             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|               <h3 class="text-lg leading-6 font-medium text-gray-900"> |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|                 Create a new Organization |                 {$_('create-a-new-user-group')} | ||||||
|               </h3> |               </h3> | ||||||
|               <div class="mt-2 mb-6"> |               <div class="mt-2 mb-6"> | ||||||
|                 <p class="text-sm text-gray-500"> |                 <p class="text-sm text-gray-500"> | ||||||
|                   Please provide the required information to add a new |                   {$_('please-provide-the-required-information-for-creating-a-new-user-group')} | ||||||
|                   organization. |  | ||||||
|                 </p> |                 </p> | ||||||
|               </div> |               </div> | ||||||
|               <div class="grid grid-cols-6 gap-6"> |               <div class="grid grid-cols-6 gap-6"> | ||||||
|                 <div class="col-span-6"> |                 <div class="col-span-6"> | ||||||
|                   <label |                   <label | ||||||
|                     for="firstname" |                     for="firstname" | ||||||
|                     class="block text-sm font-medium text-gray-700">Name</label> |                     class="block text-sm font-medium text-gray-700">{$_('name')}</label> | ||||||
|                   <input |                   <input | ||||||
|                     use:focus |                     use:focus | ||||||
|                     autocomplete="off" |                     autocomplete="off" | ||||||
|                     placeholder="Name" |                     placeholder="{$_('name')}" | ||||||
|                     class:border-red-500={!isOrgnameValid} |                     class:border-red-500={!isNameValid} | ||||||
|                     class:focus:border-red-500={!isOrgnameValid} |                     class:focus:border-red-500={!isNameValid} | ||||||
|                     class:focus:ring-red-500={!isOrgnameValid} |                     class:focus:ring-red-500={!isNameValid} | ||||||
|                     bind:value={name} |                     bind:value={name_input_value} | ||||||
|                     bind:this={name_input_dom} |  | ||||||
|                     type="text" |                     type="text" | ||||||
|                     name="firstname" |                     name="firstname" | ||||||
|                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|                   {#if !isOrgnameValid} |                   {#if !isNameValid} | ||||||
|                     <span |                     <span | ||||||
|                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|                       Organization name is required |                       {$_('name-is-required')} | ||||||
|                     </span> |                     </span> | ||||||
|                   {/if} |                   {/if} | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="trackname" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('description-optional')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder="{$_('something-about-the-group')}" | ||||||
|  |                     bind:value={description_input_value} | ||||||
|  |                     type="text" | ||||||
|  |                     name="trackname" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                 </div> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
							
								
								
									
										220
									
								
								src/components/groups/GroupDetail.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,220 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import { | ||||||
|  |     UserGroupService | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import PromiseError from "../base/PromiseError.svelte"; | ||||||
|  |   let data_loaded = false; | ||||||
|  |   export let params; | ||||||
|  |   const promise = UserGroupService.userGroupControllerGetOne(params.groupid); | ||||||
|  |   const colors = [ | ||||||
|  |     "#f3558e", | ||||||
|  |     "#17b978", | ||||||
|  |     "#3498db", | ||||||
|  |     "#3f3b3b", | ||||||
|  |     "#775ada", | ||||||
|  |     "#7ed6df_#000000", | ||||||
|  |     "#000000", | ||||||
|  |     "#21e6c1_#000000", | ||||||
|  |     "#c0392b", | ||||||
|  |     "#d35400", | ||||||
|  |     "#7f8c8d", | ||||||
|  |     "#6ab04c", | ||||||
|  |     "#4834d4", | ||||||
|  |     "#ff1f5a", | ||||||
|  |     "#eac100", | ||||||
|  |   ]; | ||||||
|  |   let matched_colors = []; | ||||||
|  |   $: delete_triggered = false; | ||||||
|  |   $: search_permission = ""; | ||||||
|  |   $: original_data = {}; | ||||||
|  |   $: editable = {}; | ||||||
|  |   $: changes_performed = !(JSON.stringify(original_data) == JSON.stringify(editable)); | ||||||
|  |   $: isGroupnameValid = editable.name !== ""; | ||||||
|  |   $: save_enabled = | ||||||
|  |     changes_performed && isGroupnameValid  | ||||||
|  |   promise.then((data) => { | ||||||
|  |     let current_target = ""; | ||||||
|  |     let colorindex = -1; | ||||||
|  |     data.permissions = data.permissions.sort(); | ||||||
|  |     data.permissions.forEach((p) => { | ||||||
|  |       const target = p.split(":")[0]; | ||||||
|  |       if (current_target !== p.split(":")[0]) { | ||||||
|  |         colorindex++; | ||||||
|  |         current_target = p.split(":")[0]; | ||||||
|  |       } | ||||||
|  |       let background = colors[colorindex]; | ||||||
|  |       let foreground = "#fff"; | ||||||
|  |       if (background.includes("_")) { | ||||||
|  |         foreground = background.split("_")[1]; | ||||||
|  |         background = background.split("_")[0]; | ||||||
|  |       } | ||||||
|  |       matched_colors[target] = [background, foreground]; | ||||||
|  |     }); | ||||||
|  |     data_loaded = true; | ||||||
|  |     original_data = Object.assign(original_data, data); | ||||||
|  |     editable = Object.assign(editable, original_data); | ||||||
|  |   }); | ||||||
|  |   function submit() { | ||||||
|  |     if (data_loaded === true && save_enabled) { | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_('updateing-group'), | ||||||
|  |         duration: 2500, | ||||||
|  |       }).showToast(); | ||||||
|  |       UserGroupService.userGroupControllerPut(original_data.id, editable) | ||||||
|  |         .then((resp) => { | ||||||
|  |           Object.assign(original_data, editable); | ||||||
|  |           original_data = editable; | ||||||
|  |           Object.assign(original_data, editable); | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_('group-updated'), | ||||||
|  |             duration: 2500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |         }) | ||||||
|  |         .catch((err) => {}); | ||||||
|  |     } else { | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   function deleteGroup() { | ||||||
|  |     UserGroupService.userGroupControllerRemove(original_data.id, true) | ||||||
|  |       .then((resp) => { | ||||||
|  |         location.replace("./"); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => {}); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#await promise} | ||||||
|  |   {$_('loading-group-detail')} | ||||||
|  | {:then} | ||||||
|  |   <section class="container p-5 select-none"> | ||||||
|  |     <div class="flex flex-row mb-4"> | ||||||
|  |       <div class="w-full"> | ||||||
|  |         <nav class="w-full flex"> | ||||||
|  |           <ol class="list-none flex flex-row items-center justify-start"> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <svg class="flex-shrink-0 w-5 h-5 mr-2" fill="currentColor" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"></path></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <a class="mr-2" href="../">{$_('groups')}</a><svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 mr-2 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><line | ||||||
|  |                   x1="5" | ||||||
|  |                   y1="12" | ||||||
|  |                   x2="19" | ||||||
|  |                   y2="12" /> | ||||||
|  |                 <polyline points="12 5 19 12 12 19" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <span class="mr-2">{editable.name}</span> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </nav> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="mb-8 text-3xl font-extrabold leading-tight"> | ||||||
|  |       {original_data.name} | ||||||
|  |       <span data-id="group_actions_${editable.id}"> | ||||||
|  |         {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:DELETE')} | ||||||
|  |           {#if delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={deleteGroup} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button> | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = !delete_triggered; | ||||||
|  |               }} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> | ||||||
|  |           {/if} | ||||||
|  |           {#if !delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = true; | ||||||
|  |               }} | ||||||
|  |               type="button" | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-group')}</button> | ||||||
|  |           {/if} | ||||||
|  |         {/if} | ||||||
|  |         {#if !delete_triggered} | ||||||
|  |           <button | ||||||
|  |             disabled={!save_enabled} | ||||||
|  |             class:opacity-50={!save_enabled} | ||||||
|  |             type="button" | ||||||
|  |             on:click={submit} | ||||||
|  |             class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> | ||||||
|  |         {/if} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |     <!--  --> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label | ||||||
|  |         for="title" | ||||||
|  |         class="font-medium text-gray-700">{$_('name')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('name')} | ||||||
|  |         type="text" | ||||||
|  |         bind:value={editable.name} | ||||||
|  |         class:border-red-500={!isGroupnameValid} | ||||||
|  |         class:focus:border-red-500={!isGroupnameValid} | ||||||
|  |         class:focus:ring-red-500={!isGroupnameValid} | ||||||
|  |         name="title" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       {#if !isGroupnameValid} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('group-name-is-required')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label | ||||||
|  |         for="firstname" | ||||||
|  |         class="font-medium text-gray-700">{$_('description')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('description')} | ||||||
|  |         type="text" | ||||||
|  |         bind:value={editable.description} | ||||||
|  |         name="firstname" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full mt-8"> | ||||||
|  |       <p class="font-medium mb-4"> | ||||||
|  |         {$_('permissions')} | ||||||
|  |         <a | ||||||
|  |           class="px-4 py-2 bg-gray-500 rounded-md text-white" | ||||||
|  |           href="/groups/{params.groupid}/permissions/">{$_('edit-permissions')}</a> | ||||||
|  |       </p> | ||||||
|  |       <div class="w-full sm:my-px sm:px-px sm:w-1/2"> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder="{$_('search-for-permission')}" | ||||||
|  |           type="text" | ||||||
|  |           bind:value={search_permission} | ||||||
|  |           class="mt-4 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" /> | ||||||
|  |       </div> | ||||||
|  |       {#each original_data.permissions as p} | ||||||
|  |         {#if p.toLowerCase().includes(search_permission.toLowerCase())} | ||||||
|  |           <span | ||||||
|  |             style="background:{matched_colors[p.split(':')[0]][0]};color:{matched_colors[p.split(':')[0]][1]};" | ||||||
|  |             class="mt-1 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-indigo-100 rounded">{p}</span> | ||||||
|  |           <!--  --> | ||||||
|  |         {/if} | ||||||
|  |       {/each} | ||||||
|  |     </div> | ||||||
|  |   </section> | ||||||
|  | {:catch error} | ||||||
|  |   <PromiseError {error} /> | ||||||
|  | {/await} | ||||||
							
								
								
									
										226
									
								
								src/components/groups/GroupPermissions.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,226 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { | ||||||
|  |     PermissionService, | ||||||
|  |     CreatePermission, | ||||||
|  | UserGroupService, | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import PromiseError from "../base/PromiseError.svelte"; | ||||||
|  |   export let params; | ||||||
|  |   let [ | ||||||
|  |     grantedPermissions_initial, | ||||||
|  |     grantedPermissions, | ||||||
|  |     to_add, | ||||||
|  |     to_delete, | ||||||
|  |     allpermissions, | ||||||
|  |     promises, | ||||||
|  |   ] = [[], [], [], [], [], []]; | ||||||
|  |   $: original_data = {}; | ||||||
|  |   $: save_enabled = | ||||||
|  |     JSON.stringify(grantedPermissions) === | ||||||
|  |     JSON.stringify(grantedPermissions_initial); | ||||||
|  |   const group_promise = UserGroupService.userGroupControllerGetOne(params.groupid); | ||||||
|  |   group_promise.then((data) => { | ||||||
|  |     original_data = Object.assign(original_data, data); | ||||||
|  |   }); | ||||||
|  |   function submit() { | ||||||
|  |     Toastify({ | ||||||
|  |       text: $_('updating-permissions'), | ||||||
|  |       duration: 2500, | ||||||
|  |     }).showToast(); | ||||||
|  |     to_delete.forEach((d) => { | ||||||
|  |       promises = promises.concat([ | ||||||
|  |         PermissionService.permissionControllerRemove(d, true), | ||||||
|  |       ]); | ||||||
|  |     }); | ||||||
|  |     to_add.forEach((a) => { | ||||||
|  |       promises = promises.concat([ | ||||||
|  |         PermissionService.permissionControllerPost(a), | ||||||
|  |       ]); | ||||||
|  |     }); | ||||||
|  |     Promise.all(promises).then((values) => { | ||||||
|  |       promises = []; | ||||||
|  |       to_delete.forEach((d) => { | ||||||
|  |         to_delete = to_delete.filter((o) => o !== d); | ||||||
|  |       }); | ||||||
|  |       to_add.forEach((a) => { | ||||||
|  |         to_add = to_add.filter( | ||||||
|  |           (o) => o.target + ":" + o.action !== a.target + ":" + a.action | ||||||
|  |         ); | ||||||
|  |       }); | ||||||
|  |       grantedPermissions_initial = grantedPermissions; | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_("permissions-updated"), | ||||||
|  |         duration: 2500, | ||||||
|  |         backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |       }).showToast(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |   Object.values(CreatePermission.target).forEach((t) => { | ||||||
|  |     Object.values(CreatePermission.action).forEach((a) => { | ||||||
|  |       allpermissions = allpermissions.concat([{ target: t, action: a }]); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   UserGroupService.userGroupControllerGetPermissions(params.groupid).then((val) => { | ||||||
|  |     val.directlyGranted.forEach((p) => { | ||||||
|  |       delete p.responseType; | ||||||
|  |       grantedPermissions = grantedPermissions.concat([p]); | ||||||
|  |     }); | ||||||
|  |     grantedPermissions_initial = grantedPermissions; | ||||||
|  |   }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#await group_promise} | ||||||
|  |   <!--  --> | ||||||
|  | {:then user} | ||||||
|  |   <section class="container p-5 select-none"> | ||||||
|  |     <div class="flex flex-row mb-4"> | ||||||
|  |       <div class="w-full"> | ||||||
|  |         <nav class="w-full flex"> | ||||||
|  |           <ol class="list-none flex flex-row items-center justify-start"> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <svg | ||||||
|  |                 class="flex-shrink-0 w-5 h-5 mr-2" | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 640 512"><path | ||||||
|  |                   fill="currentColor" | ||||||
|  |                   d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <a class="mr-2" href="../../">{$_('user-groups')}</a><svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 mr-2 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><line | ||||||
|  |                   x1="5" | ||||||
|  |                   y1="12" | ||||||
|  |                   x2="19" | ||||||
|  |                   y2="12" /> | ||||||
|  |                 <polyline points="12 5 19 12 12 19" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <span class="mr-2"><a href="../">{original_data.name}</a></span> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 mr-2 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><line | ||||||
|  |                   x1="5" | ||||||
|  |                   y1="12" | ||||||
|  |                   x2="19" | ||||||
|  |                   y2="12" /> | ||||||
|  |                 <polyline points="12 5 19 12 12 19" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <span class="mr-2">{$_('permissions')}</span> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </nav> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="mb-8 text-3xl font-extrabold"> | ||||||
|  |       {$_('permissions')}: | ||||||
|  |       {original_data.name} | ||||||
|  |       <span> | ||||||
|  |         {#if promises.length === 0} | ||||||
|  |           <button | ||||||
|  |             disabled={save_enabled} | ||||||
|  |             class:opacity-50={save_enabled} | ||||||
|  |             type="button" | ||||||
|  |             on:click={submit} | ||||||
|  |             class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> | ||||||
|  |         {:else} | ||||||
|  |           <button | ||||||
|  |             type="button" | ||||||
|  |             class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-yellow-600 text-base font-medium text-white hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('applying-changes')}</button> | ||||||
|  |         {/if} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |     <!--  --> | ||||||
|  |     <div class="flex flex-wrap -mx-1 overflow-hidden"> | ||||||
|  |       <div class="my-1 px-1 w-full overflow-hidden sm:w-1/2"> | ||||||
|  |         {$_('verfuegbare')} | ||||||
|  |       </div> | ||||||
|  |       <div class="my-1 px-1 w-full overflow-hidden sm:w-1/2"> | ||||||
|  |         {$_('granted')} | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <!--  --> | ||||||
|  |     <div class="flex flex-wrap -mx-1 overflow-hidden"> | ||||||
|  |       {#if allpermissions.length > 0} | ||||||
|  |         <div class="my-1 px-1 w-full overflow-hidden sm:w-1/2"> | ||||||
|  |           <div | ||||||
|  |             class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center"> | ||||||
|  |             {#each allpermissions as p} | ||||||
|  |               {#if !(grantedPermissions.filter((o)=>p.target == o.target && p.action == o.action).length > 0)} | ||||||
|  |                 <p | ||||||
|  |                   class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input"> | ||||||
|  |                   {p.target + ':' + p.action} | ||||||
|  |                   <button | ||||||
|  |                     on:click={() => { | ||||||
|  |                       grantedPermissions = grantedPermissions.concat([p]); | ||||||
|  |                       if (to_delete.some((o) => o === p.id)) { | ||||||
|  |                         to_delete = to_delete.filter((o) => o !== p.id); | ||||||
|  |                       } else { | ||||||
|  |                         to_add = to_add.concat([ | ||||||
|  |                           { | ||||||
|  |                             action: p.action, | ||||||
|  |                             target: p.target, | ||||||
|  |                             principal: original_data.id, | ||||||
|  |                           }, | ||||||
|  |                         ]); | ||||||
|  |                       } | ||||||
|  |                     }} | ||||||
|  |                     type="button" | ||||||
|  |                     class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-200 text-base font-medium text-black hover:bg-green-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm">+</button> | ||||||
|  |                 </p> | ||||||
|  |               {/if} | ||||||
|  |             {/each} | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="my-1 px-1 w-full overflow-hidden sm:w-1/2"> | ||||||
|  |           <div | ||||||
|  |             class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center"> | ||||||
|  |             {#each grantedPermissions as p} | ||||||
|  |               <p | ||||||
|  |                 class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input"> | ||||||
|  |                 {p.target + ':' + p.action} | ||||||
|  |                 <button | ||||||
|  |                   on:click={() => { | ||||||
|  |                     grantedPermissions = grantedPermissions.filter((o) => o.target + ':' + o.action !== p.target + ':' + p.action); | ||||||
|  |                     if (to_add.some((o) => o.target + ':' + o.action === p.target + ':' + p.action)) { | ||||||
|  |                       to_add = to_add.filter((o) => o.target + ':' + o.action !== p.target + ':' + p.action); | ||||||
|  |                     } else { | ||||||
|  |                       to_delete = to_delete.concat([p.id]); | ||||||
|  |                     } | ||||||
|  |                   }} | ||||||
|  |                   type="button" | ||||||
|  |                   class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-300 text-base font-medium text-black hover:bg-red-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm">-</button> | ||||||
|  |               </p> | ||||||
|  |             {/each} | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |   </section> | ||||||
|  | {:catch error} | ||||||
|  |   <PromiseError {error} /> | ||||||
|  | {/await} | ||||||
							
								
								
									
										29
									
								
								src/components/groups/Groups.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import AddGroupModal from "./AddGroupModal.svelte"; | ||||||
|  |   import UserGroupsOverview from "./UserGroupsOverview.svelte"; | ||||||
|  |   $: current_groups = []; | ||||||
|  |   export let modal_open = false; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <section class="container p-5"> | ||||||
|  |   <span class="mb-1 text-3xl font-extrabold leading-tight"> | ||||||
|  |     {$_('user-groups')} | ||||||
|  |     {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:CREATE')} | ||||||
|  |       <button | ||||||
|  |         on:click={() => { | ||||||
|  |           modal_open = true; | ||||||
|  |         }} | ||||||
|  |         type="button" | ||||||
|  |         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |         {$_('add-user-group')} | ||||||
|  |       </button> | ||||||
|  |     {/if} | ||||||
|  |   </span> | ||||||
|  |   <UserGroupsOverview bind:current_groups /> | ||||||
|  | </section> | ||||||
|  |  | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:CREATE')} | ||||||
|  |   <AddGroupModal bind:current_groups bind:modal_open /> | ||||||
|  | {/if} | ||||||
							
								
								
									
										12
									
								
								src/components/groups/UserGroupsEmptyState.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import groups_empty from "./groups_empty.svg"; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div class="text-center items-center justify-center"> | ||||||
|  |   <p class="mb-16 text-lg text-gray-500"> | ||||||
|  |     <img class="w-full h-44" src={groups_empty} alt="" /> | ||||||
|  |     <span class="font-bold">{$_('there-are-no-groups-yet')}.</span><br /> | ||||||
|  |     <span>{$_('add-your-first-group')}</span> | ||||||
|  |   </p> | ||||||
|  | </div> | ||||||
							
								
								
									
										126
									
								
								src/components/groups/UserGroupsOverview.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,126 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { UserGroupService } from "@odit/lfk-client-js"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import UserGroupsEmptyState from "./UserGroupsEmptyState.svelte"; | ||||||
|  |   $: searchvalue = ""; | ||||||
|  |   $: active_deletes = []; | ||||||
|  |   export let current_groups = []; | ||||||
|  |   const groups_promise = UserGroupService.userGroupControllerGetAll().then( | ||||||
|  |     (val) => { | ||||||
|  |       current_groups = val; | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')} | ||||||
|  |   {#await groups_promise} | ||||||
|  |     <div | ||||||
|  |       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||||
|  |       role="alert"> | ||||||
|  |       <p class="font-bold">{$_('groups-are-being-loaded')}</p> | ||||||
|  |       <p class="text-sm">{$_('this-might-take-a-moment')}</p> | ||||||
|  |     </div> | ||||||
|  |   {:then} | ||||||
|  |     {#if current_groups.length === 0} | ||||||
|  |       <UserGroupsEmptyState /> | ||||||
|  |     {:else} | ||||||
|  |       <input | ||||||
|  |         type="search" | ||||||
|  |         bind:value={searchvalue} | ||||||
|  |         placeholder={$_('datatable.search')} | ||||||
|  |         aria-label={$_('datatable.search')} | ||||||
|  |         class="gridjs-input gridjs-search-input mb-4" /> | ||||||
|  |       <div | ||||||
|  |         class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> | ||||||
|  |         <table class="divide-y divide-gray-200 w-full"> | ||||||
|  |           <thead class="bg-gray-50"> | ||||||
|  |             <tr> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('name')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('description')} | ||||||
|  |               </th> | ||||||
|  |               <th scope="col" class="relative px-6 py-3"> | ||||||
|  |                 <span class="sr-only">{$_('action')}</span> | ||||||
|  |               </th> | ||||||
|  |             </tr> | ||||||
|  |           </thead> | ||||||
|  |           <tbody class="divide-y divide-gray-200"> | ||||||
|  |             {#each current_groups as group} | ||||||
|  |               {#if Object.values(group) | ||||||
|  |                 .toString() | ||||||
|  |                 .toLowerCase() | ||||||
|  |                 .includes(searchvalue)} | ||||||
|  |                 <tr data-rowid="user_{group.id}"> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <div class="flex items-center"> | ||||||
|  |                       <div class="ml-4"> | ||||||
|  |                         <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                           {group.name} | ||||||
|  |                         </div> | ||||||
|  |                       </div> | ||||||
|  |                     </div> | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     {group.description} | ||||||
|  |                   </td> | ||||||
|  |                   {#if active_deletes[group.id] === true} | ||||||
|  |                     <td | ||||||
|  |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                       <button | ||||||
|  |                         on:click={() => { | ||||||
|  |                           active_deletes[group.id] = false; | ||||||
|  |                         }} | ||||||
|  |                         tabindex="0" | ||||||
|  |                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> | ||||||
|  |                       <button | ||||||
|  |                         on:click={() => { | ||||||
|  |                           UserGroupService.userGroupControllerRemove(group.id, true) | ||||||
|  |                             .then((resp) => { | ||||||
|  |                               current_groups = current_groups.filter((obj) => obj.id !== group.id); | ||||||
|  |                             }) | ||||||
|  |                             .catch((err) => { | ||||||
|  |                               // error deleting user | ||||||
|  |                             }); | ||||||
|  |                         }} | ||||||
|  |                         tabindex="0" | ||||||
|  |                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> | ||||||
|  |                     </td> | ||||||
|  |                   {:else} | ||||||
|  |                     <td | ||||||
|  |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                       <a | ||||||
|  |                         href="./{group.id}" | ||||||
|  |                         class="text-indigo-600 hover:text-indigo-900">Details</a> | ||||||
|  |                       {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:DELETE')} | ||||||
|  |                         <button | ||||||
|  |                           on:click={() => { | ||||||
|  |                             active_deletes[group.id] = true; | ||||||
|  |                           }} | ||||||
|  |                           tabindex="0" | ||||||
|  |                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> | ||||||
|  |                       {/if} | ||||||
|  |                     </td> | ||||||
|  |                   {/if} | ||||||
|  |                 </tr> | ||||||
|  |               {/if} | ||||||
|  |             {/each} | ||||||
|  |           </tbody> | ||||||
|  |         </table> | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |   {:catch error} | ||||||
|  |     <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> | ||||||
|  |       <span class="inline-block align-middle mr-8"> | ||||||
|  |         <b class="capitalize">{$_('general_promise_error')}</b> | ||||||
|  |         {error} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |   {/await} | ||||||
|  | {/if} | ||||||
							
								
								
									
										1
									
								
								src/components/groups/groups_empty.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										279
									
								
								src/components/orgs/AddOrgModal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,279 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { clickOutside } from "../base/outsideclick"; | ||||||
|  |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|  |   import { RunnerOrganizationService } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   export let modal_open; | ||||||
|  |   export let current_organizations; | ||||||
|  |   let name_input_dom; | ||||||
|  |   function focus(el) { | ||||||
|  |     el.focus(); | ||||||
|  |   } | ||||||
|  |   $: name = ""; | ||||||
|  |   $: processed_last_submit = true; | ||||||
|  |   $: isOrgnameValid = name.trim().length !== 0; | ||||||
|  |   $: isAddress1Valid = address_input1_value.trim().length !== 0; | ||||||
|  |   $: iszipcodevalid = address_zipcode_value.trim().length !== 0; | ||||||
|  |   $: iscityvalid = address_city_value.trim().length !== 0; | ||||||
|  |   $: createbtnenabled = | ||||||
|  |     isOrgnameValid && | ||||||
|  |     ((isAddress1Valid && iszipcodevalid && iscityvalid) || | ||||||
|  |       address_checked === false); | ||||||
|  |   $: address_input1_value = ""; | ||||||
|  |   $: address_input2_value = ""; | ||||||
|  |   $: address_zipcode_value = ""; | ||||||
|  |   $: address_city_value = ""; | ||||||
|  |   $: address_checked = true; | ||||||
|  |  | ||||||
|  |   let address_input1; | ||||||
|  |   let address_input2; | ||||||
|  |   let address_zipcode; | ||||||
|  |   let address_city; | ||||||
|  |  | ||||||
|  |   (() => { | ||||||
|  |     document.onkeydown = (e) => { | ||||||
|  |       e = e || window.event; | ||||||
|  |       if (e.key === "Escape") { | ||||||
|  |         modal_open = false; | ||||||
|  |       } | ||||||
|  |       if (e.keyCode === 13) { | ||||||
|  |         if (createbtnenabled === true) { | ||||||
|  |           createbtnenabled = false; | ||||||
|  |           submit(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   })(); | ||||||
|  |   function submit() { | ||||||
|  |     if (processed_last_submit === true) { | ||||||
|  |       processed_last_submit = false; | ||||||
|  |       const toast = Toastify({ | ||||||
|  |         text: $_("organization-is-being-added"), | ||||||
|  |         duration: -1, | ||||||
|  |       }).showToast(); | ||||||
|  |       let address = {}; | ||||||
|  |       if (address_checked === true) { | ||||||
|  |         address = { | ||||||
|  |           address1: address_input1_value, | ||||||
|  |           address2: address_input2_value || "", | ||||||
|  |           postalcode: address_zipcode_value, | ||||||
|  |           city: address_city_value, | ||||||
|  |           country: "DE", | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |       RunnerOrganizationService.runnerOrganizationControllerPost({ | ||||||
|  |         name, | ||||||
|  |         address: address, | ||||||
|  |         contact: undefined, | ||||||
|  |       }) | ||||||
|  |         .then((result) => { | ||||||
|  |           name = ""; | ||||||
|  |           modal_open = false; | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_("organization-added"), | ||||||
|  |             duration: 500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |           current_organizations = current_organizations.concat([result]); | ||||||
|  |         }) | ||||||
|  |         .catch((err) => {}) | ||||||
|  |         .finally(() => { | ||||||
|  |           processed_last_submit = true; | ||||||
|  |           toast.hideToast(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if modal_open} | ||||||
|  |   <div | ||||||
|  |     class="fixed z-10 inset-0 overflow-y-auto" | ||||||
|  |     use:focusTrap | ||||||
|  |     use:clickOutside | ||||||
|  |     on:click_outside={() => { | ||||||
|  |       modal_open = false; | ||||||
|  |     }}> | ||||||
|  |     <div | ||||||
|  |       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||||
|  |       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||||
|  |         <div | ||||||
|  |           class="absolute inset-0 bg-gray-500 opacity-75" | ||||||
|  |           data-id="modal_backdrop" /> | ||||||
|  |       </div> | ||||||
|  |       <span | ||||||
|  |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|  |         aria-hidden="true">​</span> | ||||||
|  |       <div | ||||||
|  |         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|  |         role="dialog" | ||||||
|  |         aria-modal="true" | ||||||
|  |         aria-labelledby="modal-headline"> | ||||||
|  |         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||||
|  |           <div class="sm:flex sm:items-start"> | ||||||
|  |             <div | ||||||
|  |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|  |               <svg | ||||||
|  |                 class="h-6 w-6 text-blue-600" | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path | ||||||
|  |                   d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|  |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |                 {$_('create-a-new-organization')} | ||||||
|  |               </h3> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <p class="text-sm text-gray-500"> | ||||||
|  |                   {$_('please-provide-the-required-information-to-add-a-new-organization')} | ||||||
|  |                 </p> | ||||||
|  |               </div> | ||||||
|  |               <div class="grid grid-cols-6 gap-6"> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="firstname" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('name')}</label> | ||||||
|  |                   <input | ||||||
|  |                     use:focus | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('name')} | ||||||
|  |                     class:border-red-500={!isOrgnameValid} | ||||||
|  |                     class:focus:border-red-500={!isOrgnameValid} | ||||||
|  |                     class:focus:ring-red-500={!isOrgnameValid} | ||||||
|  |                     bind:value={name} | ||||||
|  |                     bind:this={name_input_dom} | ||||||
|  |                     type="text" | ||||||
|  |                     name="firstname" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !isOrgnameValid} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('organization-name-is-required')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="flex items-start"> | ||||||
|  |                   <div class="flex items-center h-5"> | ||||||
|  |                     <input | ||||||
|  |                       bind:checked={address_checked} | ||||||
|  |                       id="comments" | ||||||
|  |                       name="comments" | ||||||
|  |                       type="checkbox" | ||||||
|  |                       class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |                   </div> | ||||||
|  |                   <div class="ml-3 text-sm"> | ||||||
|  |                     <label | ||||||
|  |                       for="comments" | ||||||
|  |                       class="font-medium text-gray-700">{$_('address')}</label> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               {#if address_checked === true} | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="address1" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('address')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder="{$_('address')}" | ||||||
|  |                     class:border-red-500={!isAddress1Valid} | ||||||
|  |                     class:focus:border-red-500={!isAddress1Valid} | ||||||
|  |                     class:focus:ring-red-500={!isAddress1Valid} | ||||||
|  |                     bind:value={address_input1_value} | ||||||
|  |                     bind:this={address_input1} | ||||||
|  |                     type="text" | ||||||
|  |                     name="address1" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !isAddress1Valid} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('address-is-required')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="address2" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder="{$_('apartment-suite-etc')}" | ||||||
|  |                     bind:value={address_input2_value} | ||||||
|  |                     bind:this={address_input2} | ||||||
|  |                     type="text" | ||||||
|  |                     name="address2" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="zipcode" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder="{$_('zip-postal-code')}" | ||||||
|  |                     class:border-red-500={!iszipcodevalid} | ||||||
|  |                     class:focus:border-red-500={!iszipcodevalid} | ||||||
|  |                     class:focus:ring-red-500={!iszipcodevalid} | ||||||
|  |                     bind:value={address_zipcode_value} | ||||||
|  |                     bind:this={address_zipcode} | ||||||
|  |                     type="text" | ||||||
|  |                     name="zipcode" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !iszipcodevalid} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('valid-zipcode-postal-code-is-required')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="city" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('city')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder="{$_('city')}" | ||||||
|  |                     class:border-red-500={!iscityvalid} | ||||||
|  |                     class:focus:border-red-500={!iscityvalid} | ||||||
|  |                     class:focus:ring-red-500={!iscityvalid} | ||||||
|  |                     bind:value={address_city_value} | ||||||
|  |                     bind:this={address_city} | ||||||
|  |                     type="text" | ||||||
|  |                     name="city" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                   {#if !iscityvalid} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('valid-city-is-required')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |               {/if} | ||||||
|  |             </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||||
|  |           <button | ||||||
|  |             disabled={!createbtnenabled} | ||||||
|  |             class:opacity-50={!createbtnenabled} | ||||||
|  |             on:click={submit} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('create')} | ||||||
|  |           </button> | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |               modal_open = false; | ||||||
|  |             }} | ||||||
|  |             type="button" | ||||||
|  |             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('cancel')} | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   import { clickOutside } from "./outsideclick"; |   import { clickOutside } from "../base/outsideclick"; | ||||||
|   import { focusTrap } from "svelte-focus-trap"; |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|   import { RunnerOrganizationService } from "@odit/lfk-client-js"; |   import { RunnerOrganizationService } from "@odit/lfk-client-js"; | ||||||
|   import Toastify from "toastify-js"; |   import Toastify from "toastify-js"; | ||||||
| @@ -25,9 +25,7 @@ | |||||||
|         }).showToast(); |         }).showToast(); | ||||||
|         location.replace("./"); |         location.replace("./"); | ||||||
|       }) |       }) | ||||||
|       .catch((err) => { |       .catch((err) => {}); | ||||||
|         // |  | ||||||
|       }); |  | ||||||
|   } |   } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| @@ -68,13 +66,17 @@ | |||||||
|             </div> |             </div> | ||||||
|             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|               <h3 class="text-lg leading-6 font-medium text-gray-900"> |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|                 Attention! |                 {$_('attention')} | ||||||
|               </h3> |               </h3> | ||||||
|               <div class="mt-2 mb-6"> |               <div class="mt-2 mb-6"> | ||||||
|                 <p class="text-sm text-gray-500"> |                 <p class="text-sm text-gray-500"> | ||||||
|                   Do you want to delete the organization |                   {$_( | ||||||
|                   {delete_org.name}?<br />All associated teams and runners will |                     'do-you-want-to-delete-the-organization-delete_org-name', | ||||||
|                   be deleted too! |                     { | ||||||
|  |                       values: { orgname: delete_org.name }, | ||||||
|  |                     } | ||||||
|  |                   )}<br /> | ||||||
|  |                   {$_('all-associated-teams-and-runners-will-be-deleted-too')} | ||||||
|                 </p> |                 </p> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
| @@ -85,13 +87,13 @@ | |||||||
|             on:click={deleteOrg} |             on:click={deleteOrg} | ||||||
|             type="button" |             type="button" | ||||||
|             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|             Confirm, delete organization and associated teams+runners. |             {$_('confirm-delete-organization-and-associated-teams-runners')} | ||||||
|           </button> |           </button> | ||||||
|           <button |           <button | ||||||
|             on:click={cancelDelete} |             on:click={cancelDelete} | ||||||
|             type="button" |             type="button" | ||||||
|             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> |             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|             Cancel, keep organization |             {$_('cancel-keep-organization')} | ||||||
|           </button> |           </button> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
							
								
								
									
										485
									
								
								src/components/orgs/OrgDetail.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,485 @@ | |||||||
|  | <script> | ||||||
|  |   import { | ||||||
|  |     GroupContactService, | ||||||
|  |     RunnerOrganizationService, | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import { getLocaleFromNavigator, _ } from "svelte-i18n"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte"; | ||||||
|  |   import ImportRunnerModal from "../runners/ImportRunnerModal.svelte"; | ||||||
|  |   import PromiseError from "../base/PromiseError.svelte"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|  |   $: delete_triggered = false; | ||||||
|  |   $: address_valid_or_none = | ||||||
|  |     (isAddress1Valid && iszipcodevalid && iscityvalid) || | ||||||
|  |     editable.address_checked === false; | ||||||
|  |   $: save_enabled = data_changed && address_valid_or_none; | ||||||
|  |   let original = ""; | ||||||
|  |   let original_object = {}; | ||||||
|  |   let contacts = []; | ||||||
|  |   export let params; | ||||||
|  |   $: editable = {}; | ||||||
|  |   $: contact = {}; | ||||||
|  |   $: data_loaded = false; | ||||||
|  |   $: data_changed = !(JSON.stringify(editable) === original); | ||||||
|  |   $: isAddress1Valid = editable.address?.address1?.trim().length !== 0; | ||||||
|  |   $: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0; | ||||||
|  |   $: iscityvalid = editable.address?.city?.trim().length !== 0; | ||||||
|  |   $: sponsoring_contracts_download_open = false; | ||||||
|  |   const getContactLabel = (option) => | ||||||
|  |     option.firstname + " " + (option.middlename || "") + " " + option.lastname; | ||||||
|  |   const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne( | ||||||
|  |     params.orgid | ||||||
|  |   ).then((value) => { | ||||||
|  |     data_loaded = true; | ||||||
|  |     value.address_checked = value.address.address1 !== null; | ||||||
|  |     if (value.address_checked === false) { | ||||||
|  |       value.address = { | ||||||
|  |         address1: "", | ||||||
|  |         address2: "", | ||||||
|  |         city: "", | ||||||
|  |         postalcode: "", | ||||||
|  |         country: "", | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     editable = Object.assign(editable, value); | ||||||
|  |     editable = editable; | ||||||
|  |     original_object = Object.assign(editable, value); | ||||||
|  |     original = JSON.stringify(value); | ||||||
|  |     GroupContactService.groupContactControllerGetAll().then((val) => { | ||||||
|  |       contacts = val.map((r) => { | ||||||
|  |         return { label: getContactLabel(r), value: r }; | ||||||
|  |       }); | ||||||
|  |       if (editable.contact) { | ||||||
|  |         contact = contacts.find((g) => g.value.id == editable.contact.id); | ||||||
|  |       } else { | ||||||
|  |         contact = null; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   let modal_open = false; | ||||||
|  |   let delete_org = {}; | ||||||
|  |   document.addEventListener("click", function (e) { | ||||||
|  |     if ( | ||||||
|  |       e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && | ||||||
|  |       e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" | ||||||
|  |     ) { | ||||||
|  |       sponsoring_contracts_download_open = false; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   function deleteOrganization() { | ||||||
|  |     RunnerOrganizationService.runnerOrganizationControllerRemove( | ||||||
|  |       original_object.id, | ||||||
|  |       false | ||||||
|  |     ) | ||||||
|  |       .then((resp) => { | ||||||
|  |         Toastify({ | ||||||
|  |           text: $_("organization-deleted"), | ||||||
|  |           duration: 500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |         location.replace("./"); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => { | ||||||
|  |         modal_open = true; | ||||||
|  |         delete_org = original_object; | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  |   function submit() { | ||||||
|  |     if (data_loaded === true && save_enabled) { | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_("updating-organization"), | ||||||
|  |         duration: 2500, | ||||||
|  |       }).showToast(); | ||||||
|  |       let postdata = Object.assign({}, editable); | ||||||
|  |       if (postdata.address_checked === false) { | ||||||
|  |         postdata.address = null; | ||||||
|  |       } | ||||||
|  |       postdata.contact = postdata.contact?.id; | ||||||
|  |       RunnerOrganizationService.runnerOrganizationControllerPut( | ||||||
|  |         original_object.id, | ||||||
|  |         postdata | ||||||
|  |       ) | ||||||
|  |         .then((resp) => { | ||||||
|  |           original_object = Object.assign({}, editable); | ||||||
|  |           original = JSON.stringify(original_object); | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_("updated-organization"), | ||||||
|  |             duration: 2500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |         }) | ||||||
|  |         .catch((err) => {}); | ||||||
|  |     } else { | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   export let import_modal_open = false; | ||||||
|  |   async function generateSponsoringContract(locale) { | ||||||
|  |     sponsoring_contracts_download_open = false; | ||||||
|  |     const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners( | ||||||
|  |       original_object.id | ||||||
|  |     ); | ||||||
|  |     const toast = Toastify({ | ||||||
|  |       text: $_("generating-pdf"), | ||||||
|  |       duration: -1, | ||||||
|  |     }).showToast(); | ||||||
|  |     fetch( | ||||||
|  |       `${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, | ||||||
|  |       { | ||||||
|  |         method: "POST", | ||||||
|  |         headers: { | ||||||
|  |           "Content-Type": "application/json", | ||||||
|  |         }, | ||||||
|  |         body: JSON.stringify(runners), | ||||||
|  |       } | ||||||
|  |     ) | ||||||
|  |       .then((response) => { | ||||||
|  |         if (response.status != "200") { | ||||||
|  |           toast.hideToast(); | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_("pdf-generation-failed"), | ||||||
|  |             duration: 3500, | ||||||
|  |             backgroundColor: | ||||||
|  |               "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", | ||||||
|  |           }).showToast(); | ||||||
|  |         } else { | ||||||
|  |           return response.blob(); | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |       .then((blob) => { | ||||||
|  |         const url = window.URL.createObjectURL(blob); | ||||||
|  |         let a = document.createElement("a"); | ||||||
|  |         a.href = url; | ||||||
|  |         a.download = "Sponsorings_" + original_object.name + ".pdf"; | ||||||
|  |         document.body.appendChild(a); | ||||||
|  |         a.click(); | ||||||
|  |         a.remove(); | ||||||
|  |         toast.hideToast(); | ||||||
|  |         Toastify({ | ||||||
|  |           text: $_("pdf-successfully-generated"), | ||||||
|  |           duration: 3500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => {}); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <ImportRunnerModal | ||||||
|  |   on:cancelDelete={(event) => { | ||||||
|  |     import_modal_open = false; | ||||||
|  |   }} | ||||||
|  |   current_runners={[]} | ||||||
|  |   passed_team={{}} | ||||||
|  |   passed_orgs={[]} | ||||||
|  |   passed_org={editable} | ||||||
|  |   opened_from="OrgDetail" | ||||||
|  |   bind:import_modal_open /> | ||||||
|  | <ConfirmOrgDeletion bind:modal_open bind:delete_org /> | ||||||
|  | {#if data_loaded} | ||||||
|  |   <section class="container p-5"> | ||||||
|  |     <div class="mb-8 text-3xl font-extrabold leading-tight"> | ||||||
|  |       {original_object.name} | ||||||
|  |       <span data-id="org_actions_${editable.id}"> | ||||||
|  |         <div id="sponsoring:dropdown" class="relative inline-block"> | ||||||
|  |           <div> | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 sponsoring_contracts_download_open = !sponsoring_contracts_download_open; | ||||||
|  |               }} | ||||||
|  |               type="button" | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex" | ||||||
|  |               id="options-menu" | ||||||
|  |               aria-haspopup="true" | ||||||
|  |               aria-expanded="true"> | ||||||
|  |               {$_('generate-sponsoring-contracts')} | ||||||
|  |               <svg | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 class="-mr-1 ml-2 h-5 w-5"><path | ||||||
|  |                   fill="none" | ||||||
|  |                   d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   fill="currentColor" | ||||||
|  |                   d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg> | ||||||
|  |             </button> | ||||||
|  |           </div> | ||||||
|  |           {#if sponsoring_contracts_download_open} | ||||||
|  |             <div | ||||||
|  |               class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5" | ||||||
|  |               id="sponsoring:dropdown:menu"> | ||||||
|  |               <div | ||||||
|  |                 class="py-1" | ||||||
|  |                 role="menu" | ||||||
|  |                 aria-orientation="vertical" | ||||||
|  |                 aria-labelledby="options-menu"> | ||||||
|  |                 <span | ||||||
|  |                   class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> | ||||||
|  |                 <button | ||||||
|  |                   on:click={() => { | ||||||
|  |                     generateSponsoringContract('de'); | ||||||
|  |                   }} | ||||||
|  |                   type="submit" | ||||||
|  |                   class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" | ||||||
|  |                   role="menuitem"> | ||||||
|  |                   {$_('german')} | ||||||
|  |                 </button> | ||||||
|  |                 <button | ||||||
|  |                   on:click={() => { | ||||||
|  |                     generateSponsoringContract('en'); | ||||||
|  |                   }} | ||||||
|  |                   type="submit" | ||||||
|  |                   class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" | ||||||
|  |                   role="menuitem"> | ||||||
|  |                   {$_('english')} | ||||||
|  |                 </button> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           {/if} | ||||||
|  |         </div> | ||||||
|  |         {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |               import_modal_open = true; | ||||||
|  |             }} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('import-runners')} | ||||||
|  |           </button> | ||||||
|  |         {/if} | ||||||
|  |         {#if store.state.jwtinfo.userdetails.permissions.includes('USER:DELETE')} | ||||||
|  |           {#if delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={deleteOrganization} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button> | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = !delete_triggered; | ||||||
|  |               }} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> | ||||||
|  |           {/if} | ||||||
|  |           {#if !delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = true; | ||||||
|  |               }} | ||||||
|  |               type="button" | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-organization')}</button> | ||||||
|  |           {/if} | ||||||
|  |         {/if} | ||||||
|  |         {#if !delete_triggered} | ||||||
|  |           <button | ||||||
|  |             on:click={submit} | ||||||
|  |             disabled={!save_enabled} | ||||||
|  |             class:opacity-50={!save_enabled} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> | ||||||
|  |         {/if} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |     <div class="flex flex-row mb-4"> | ||||||
|  |       <div class="w-full"> | ||||||
|  |         <nav class="w-full flex"> | ||||||
|  |           <ol class="list-none flex flex-row items-center justify-start"> | ||||||
|  |             <li class="mr-2 flex items-center"> | ||||||
|  |               <svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><path | ||||||
|  |                   d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> | ||||||
|  |                 <polyline points="9 22 9 12 15 12 15 22" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <a class="mr-2" href="/">{$_('home')}</a><svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 mr-2 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><line | ||||||
|  |                   x1="5" | ||||||
|  |                   y1="12" | ||||||
|  |                   x2="19" | ||||||
|  |                   y2="12" /> | ||||||
|  |                 <polyline points="12 5 19 12 12 19" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="mr-2 flex items-center"> | ||||||
|  |               <svg | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   d="M21 20h2v2H1v-2h2V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v17zm-2 0V4H5v16h14zM8 11h3v2H8v-2zm0-4h3v2H8V7zm0 8h3v2H8v-2zm5 0h3v2h-3v-2zm0-4h3v2h-3v-2zm0-4h3v2h-3V7z" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <a class="mr-2" href="./">{$_('organizations')}</a><svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 mr-2 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><line | ||||||
|  |                   x1="5" | ||||||
|  |                   y1="12" | ||||||
|  |                   x2="19" | ||||||
|  |                   y2="12" /> | ||||||
|  |                 <polyline points="12 5 19 12 12 19" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <span class="mr-2">Org-Details #{params.orgid}</span> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </nav> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label for="name" class="font-medium text-gray-700">{$_('name')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('name')} | ||||||
|  |         type="text" | ||||||
|  |         bind:value={editable.name} | ||||||
|  |         name="name" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label | ||||||
|  |         for="contact" | ||||||
|  |         class="font-medium text-gray-700">{$_('contact')}</label> | ||||||
|  |       <Select | ||||||
|  |         containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|  |         itemFilter={(label, filterText, option) => label | ||||||
|  |             .toLowerCase() | ||||||
|  |             .includes( | ||||||
|  |               filterText.toLowerCase() | ||||||
|  |             ) || option.value.id | ||||||
|  |             .toString() | ||||||
|  |             .startsWith(filterText.toLowerCase())} | ||||||
|  |         items={contacts} | ||||||
|  |         showChevron={true} | ||||||
|  |         placeholder={$_('no-contact-selected')} | ||||||
|  |         noOptionsMessage={$_('no-contact-found')} | ||||||
|  |         bind:selectedValue={contact} | ||||||
|  |         on:select={(selectedValue) => (editable.contact = selectedValue.detail.value)} | ||||||
|  |         on:clear={() => (editable.contact = null)} /> | ||||||
|  |     </div> | ||||||
|  |     <!--  --> | ||||||
|  |     <div class="flex items-start mt-2"> | ||||||
|  |       <div class="flex items-center h-5"> | ||||||
|  |         <input | ||||||
|  |           bind:checked={editable.address_checked} | ||||||
|  |           id="comments" | ||||||
|  |           name="comments" | ||||||
|  |           type="checkbox" | ||||||
|  |           class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |       </div> | ||||||
|  |       <div class="ml-3 text-sm"> | ||||||
|  |         <label | ||||||
|  |           for="comments" | ||||||
|  |           class="font-medium text-gray-700">{$_('address')}</label> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     {#if editable.address_checked === true} | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="address1" | ||||||
|  |           class="block text-sm font-medium text-gray-700">{$_('address')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder="Address" | ||||||
|  |           class:border-red-500={!isAddress1Valid} | ||||||
|  |           class:focus:border-red-500={!isAddress1Valid} | ||||||
|  |           class:focus:ring-red-500={!isAddress1Valid} | ||||||
|  |           bind:value={editable.address.address1} | ||||||
|  |           type="text" | ||||||
|  |           name="address1" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |         {#if !isAddress1Valid} | ||||||
|  |           <span | ||||||
|  |             class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |             {$_('address-is-required')} | ||||||
|  |           </span> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="address2" | ||||||
|  |           class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder={$_('apartment-suite-etc')} | ||||||
|  |           bind:value={editable.address.address2} | ||||||
|  |           type="text" | ||||||
|  |           name="address2" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |       </div> | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="zipcode" | ||||||
|  |           class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder={$_('zip-postal-code')} | ||||||
|  |           class:border-red-500={!iszipcodevalid} | ||||||
|  |           class:focus:border-red-500={!iszipcodevalid} | ||||||
|  |           class:focus:ring-red-500={!iszipcodevalid} | ||||||
|  |           bind:value={editable.address.postalcode} | ||||||
|  |           type="text" | ||||||
|  |           name="zipcode" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |         {#if !iszipcodevalid} | ||||||
|  |           <span | ||||||
|  |             class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |             {$_('valid-zipcode-postal-code-is-required')} | ||||||
|  |           </span> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |       <div class="col-span-6"> | ||||||
|  |         <label | ||||||
|  |           for="city" | ||||||
|  |           class="block text-sm font-medium text-gray-700">{$_('city')}</label> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           placeholder={$_('city')} | ||||||
|  |           class:border-red-500={!iscityvalid} | ||||||
|  |           class:focus:border-red-500={!iscityvalid} | ||||||
|  |           class:focus:ring-red-500={!iscityvalid} | ||||||
|  |           bind:value={editable.address.city} | ||||||
|  |           type="text" | ||||||
|  |           name="city" | ||||||
|  |           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |         {#if !iscityvalid} | ||||||
|  |           <span | ||||||
|  |             class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |             {$_('valid-city-is-required')} | ||||||
|  |           </span> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |   </section> | ||||||
|  | {:else} | ||||||
|  |   {#await promise} | ||||||
|  |     {$_('organization-detail-is-being-loaded')} | ||||||
|  |   {:catch error} | ||||||
|  |     <PromiseError /> | ||||||
|  |   {/await} | ||||||
|  | {/if} | ||||||
							
								
								
									
										318
									
								
								src/components/orgs/OrgOverview.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,318 @@ | |||||||
|  | <script> | ||||||
|  |   import { getLocaleFromNavigator, _ } from "svelte-i18n"; | ||||||
|  |   let modal_open = false; | ||||||
|  |   let delete_org = {}; | ||||||
|  |   import { RunnerOrganizationService } from "@odit/lfk-client-js"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import OrgsEmptyState from "./OrgsEmptyState.svelte"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte"; | ||||||
|  |   $: searchvalue = ""; | ||||||
|  |   $: active_deletes = []; | ||||||
|  |   $: sponsoring_contracts_download_open = false; | ||||||
|  |   export let current_organizations = []; | ||||||
|  |  | ||||||
|  |   const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then( | ||||||
|  |     (val) => { | ||||||
|  |       current_organizations = val; | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   document.addEventListener("click", function (e) { | ||||||
|  |     if ( | ||||||
|  |       e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && | ||||||
|  |       e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" | ||||||
|  |     ) { | ||||||
|  |       sponsoring_contracts_download_open = false; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   async function generateSponsoringContract(locale) { | ||||||
|  |     sponsoring_contracts_download_open = false; | ||||||
|  |     const orgs = current_organizations.filter((r) => r.is_selected === true); | ||||||
|  |     const toast = Toastify({ | ||||||
|  |       text: $_("generating-pdfs"), | ||||||
|  |       duration: -1, | ||||||
|  |     }).showToast(); | ||||||
|  |     let count = 0; | ||||||
|  |     for await (const o of orgs) { | ||||||
|  |       const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners( | ||||||
|  |         o.id | ||||||
|  |       ); | ||||||
|  |       fetch( | ||||||
|  |         `${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, | ||||||
|  |         { | ||||||
|  |           method: "POST", | ||||||
|  |           headers: { | ||||||
|  |             "Content-Type": "application/json", | ||||||
|  |           }, | ||||||
|  |           body: JSON.stringify(runners), | ||||||
|  |         } | ||||||
|  |       ) | ||||||
|  |         .then((response) => { | ||||||
|  |           if (response.status != "200") { | ||||||
|  |             toast.hideToast(); | ||||||
|  |             Toastify({ | ||||||
|  |               text: $_("pdf-generation-failed"), | ||||||
|  |               duration: 3500, | ||||||
|  |               backgroundColor: | ||||||
|  |                 "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", | ||||||
|  |             }).showToast(); | ||||||
|  |           } else { | ||||||
|  |             return response.blob(); | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |         .then((blob) => { | ||||||
|  |           count++; | ||||||
|  |           const url = window.URL.createObjectURL(blob); | ||||||
|  |           let a = document.createElement("a"); | ||||||
|  |           a.href = url; | ||||||
|  |           a.download = "Sponsorings_" + o.name + ".pdf"; | ||||||
|  |           document.body.appendChild(a); | ||||||
|  |           a.click(); | ||||||
|  |           a.remove(); | ||||||
|  |           if (count === orgs.length) { | ||||||
|  |             toast.hideToast(); | ||||||
|  |             Toastify({ | ||||||
|  |               text: $_("pdfs-successfully-generated"), | ||||||
|  |               duration: 3500, | ||||||
|  |               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |             }).showToast(); | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |         .catch((err) => {}); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <ConfirmOrgDeletion | ||||||
|  |   on:cancelDelete={(event) => { | ||||||
|  |     modal_open = false; | ||||||
|  |     active_deletes[event.detail.id] = false; | ||||||
|  |   }} | ||||||
|  |   bind:modal_open | ||||||
|  |   bind:delete_org /> | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')} | ||||||
|  |   {#await promise} | ||||||
|  |     <div | ||||||
|  |       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||||
|  |       role="alert"> | ||||||
|  |       <p class="font-bold">{$_('organizations-are-being-loaded')}</p> | ||||||
|  |       <p class="text-sm">{$_('this-might-take-a-moment')}</p> | ||||||
|  |     </div> | ||||||
|  |   {:then} | ||||||
|  |     {#if current_organizations.length === 0} | ||||||
|  |       <OrgsEmptyState /> | ||||||
|  |     {:else} | ||||||
|  |       <input | ||||||
|  |         type="search" | ||||||
|  |         bind:value={searchvalue} | ||||||
|  |         placeholder={$_('datatable.search')} | ||||||
|  |         aria-label={$_('datatable.search')} | ||||||
|  |         class="gridjs-input gridjs-search-input mb-4" /> | ||||||
|  |       <div class="h-12"> | ||||||
|  |         {#if current_organizations.some((r) => r.is_selected === true)} | ||||||
|  |         <div id="sponsoring:dropdown" class="relative inline-block"> | ||||||
|  |           <div> | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 sponsoring_contracts_download_open = !sponsoring_contracts_download_open; | ||||||
|  |               }} | ||||||
|  |               type="button" | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex" | ||||||
|  |               id="options-menu" | ||||||
|  |               aria-haspopup="true" | ||||||
|  |               aria-expanded="true"> | ||||||
|  |               {$_('generate-sponsoring-contracts')} | ||||||
|  |               <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="-mr-1 ml-2 h-5 w-5"><path fill="none" d="M0 0h24v24H0z"/><path fill="currentColor" d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"/></svg> | ||||||
|  |             </button> | ||||||
|  |           </div> | ||||||
|  |           {#if sponsoring_contracts_download_open} | ||||||
|  |             <div | ||||||
|  |               class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5" | ||||||
|  |               id="sponsoring:dropdown:menu"> | ||||||
|  |               <div | ||||||
|  |                 class="py-1" | ||||||
|  |                 role="menu" | ||||||
|  |                 aria-orientation="vertical" | ||||||
|  |                 aria-labelledby="options-menu"> | ||||||
|  |                 <span | ||||||
|  |                   class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> | ||||||
|  |                 <button | ||||||
|  |                   on:click={() => { | ||||||
|  |                     generateSponsoringContract('de'); | ||||||
|  |                   }} | ||||||
|  |                   type="submit" | ||||||
|  |                   class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" | ||||||
|  |                   role="menuitem"> | ||||||
|  |                   {$_('german')} | ||||||
|  |                 </button> | ||||||
|  |                 <button | ||||||
|  |                   on:click={() => { | ||||||
|  |                     generateSponsoringContract('en'); | ||||||
|  |                   }} | ||||||
|  |                   type="submit" | ||||||
|  |                   class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" | ||||||
|  |                   role="menuitem"> | ||||||
|  |                   {$_('english')} | ||||||
|  |                 </button> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           {/if} | ||||||
|  |         </div> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |       <div | ||||||
|  |         class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> | ||||||
|  |         <table class="divide-y divide-gray-200 w-full"> | ||||||
|  |           <thead class="bg-gray-50"> | ||||||
|  |             <tr> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 <span | ||||||
|  |                   on:click={() => { | ||||||
|  |                     const newstate = !current_organizations.some((r) => r.is_selected === true); | ||||||
|  |                     current_organizations = current_organizations.map((r) => { | ||||||
|  |                       r.is_selected = newstate; | ||||||
|  |                       return r; | ||||||
|  |                     }); | ||||||
|  |                   }} | ||||||
|  |                   class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)} | ||||||
|  |                     {$_('deselect-all')} | ||||||
|  |                   {:else}{$_('select-all')}{/if} | ||||||
|  |                 </span> | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('name')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('address')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('contact')} | ||||||
|  |               </th> | ||||||
|  |               <th scope="col" class="relative px-6 py-3"> | ||||||
|  |                 <span class="sr-only">{$_('action')}</span> | ||||||
|  |               </th> | ||||||
|  |             </tr> | ||||||
|  |           </thead> | ||||||
|  |           <tbody class="divide-y divide-gray-200"> | ||||||
|  |             {#each current_organizations as o} | ||||||
|  |               {#if Object.values(o) | ||||||
|  |                 .toString() | ||||||
|  |                 .toLowerCase() | ||||||
|  |                 .includes(searchvalue)} | ||||||
|  |                 <tr data-rowid="org_{o.id}"> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <input | ||||||
|  |                       bind:checked={o.is_selected} | ||||||
|  |                       type="checkbox" | ||||||
|  |                       class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <div class="flex items-center"> | ||||||
|  |                       <div class="ml-4"> | ||||||
|  |                         <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                           {o.name} | ||||||
|  |                         </div> | ||||||
|  |                       </div> | ||||||
|  |                     </div> | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <div class="flex items-center"> | ||||||
|  |                       <div class="ml-4"> | ||||||
|  |                         <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                           {#if o.address.address1 !== null} | ||||||
|  |                             {o.address.address1}<br /> | ||||||
|  |                             {o.address.address2 || ''}<br /> | ||||||
|  |                             {o.address.postalcode} | ||||||
|  |                             {o.address.city} | ||||||
|  |                             {o.address.country} | ||||||
|  |                           {/if} | ||||||
|  |                         </div> | ||||||
|  |                       </div> | ||||||
|  |                     </div> | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <div class="flex items-center"> | ||||||
|  |                       <div class="ml-4"> | ||||||
|  |                         <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                           {#if o.contact} | ||||||
|  |                             <a | ||||||
|  |                               href="../contacts/{o.contact.id}" | ||||||
|  |                               class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname} | ||||||
|  |                               {o.contact.middlename || ''} | ||||||
|  |                               {o.contact.lastname}</a> | ||||||
|  |                           {:else}{$_('no-contact-specified')}{/if} | ||||||
|  |                         </div> | ||||||
|  |                       </div> | ||||||
|  |                     </div> | ||||||
|  |                   </td> | ||||||
|  |                   {#if active_deletes[o.id] === true} | ||||||
|  |                     <td | ||||||
|  |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                       <button | ||||||
|  |                         on:click={() => { | ||||||
|  |                           active_deletes[o.id] = false; | ||||||
|  |                         }} | ||||||
|  |                         tabindex="0" | ||||||
|  |                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> | ||||||
|  |                       <button | ||||||
|  |                         on:click={() => { | ||||||
|  |                           RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false) | ||||||
|  |                             .then((resp) => { | ||||||
|  |                               current_organizations = current_organizations.filter((obj) => obj.id !== o.id); | ||||||
|  |                               Toastify({ | ||||||
|  |                                 text: 'Organization deleted', | ||||||
|  |                                 duration: 500, | ||||||
|  |                                 backgroundColor: | ||||||
|  |                                   'linear-gradient(to right, #00b09b, #96c93d)', | ||||||
|  |                               }).showToast(); | ||||||
|  |                             }) | ||||||
|  |                             .catch((err) => { | ||||||
|  |                               modal_open = true; | ||||||
|  |                               delete_org = o; | ||||||
|  |                             }); | ||||||
|  |                         }} | ||||||
|  |                         tabindex="0" | ||||||
|  |                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> | ||||||
|  |                     </td> | ||||||
|  |                   {:else} | ||||||
|  |                     <td | ||||||
|  |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                       <a | ||||||
|  |                         href="./{o.id}" | ||||||
|  |                         class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> | ||||||
|  |                       {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')} | ||||||
|  |                         <button | ||||||
|  |                           on:click={() => { | ||||||
|  |                             active_deletes[o.id] = true; | ||||||
|  |                           }} | ||||||
|  |                           tabindex="0" | ||||||
|  |                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> | ||||||
|  |                       {/if} | ||||||
|  |                     </td> | ||||||
|  |                   {/if} | ||||||
|  |                 </tr> | ||||||
|  |               {/if} | ||||||
|  |             {/each} | ||||||
|  |           </tbody> | ||||||
|  |         </table> | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |   {:catch error} | ||||||
|  |     <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> | ||||||
|  |       <span class="inline-block align-middle mr-8"> | ||||||
|  |         <b class="capitalize">{$_('general_promise_error')}</b> | ||||||
|  |         {error} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |   {/await} | ||||||
|  | {/if} | ||||||
| @@ -1,10 +1,10 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   import store from "../store"; |   import store from "../../store"; | ||||||
|   import AddOrgModal from "./AddOrgModal.svelte"; |   import AddOrgModal from "./AddOrgModal.svelte"; | ||||||
|   export let modal_open = false; |   export let modal_open = false; | ||||||
|   import OrgOverview from "./OrgOverview.svelte"; |   import OrgOverview from "./OrgOverview.svelte"; | ||||||
|   import ImportRunnerModal from "./ImportRunnerModal.svelte"; |   import ImportRunnerModal from "../runners/ImportRunnerModal.svelte"; | ||||||
|   let current_organizations = []; |   let current_organizations = []; | ||||||
|   export let import_modal_open = false; |   export let import_modal_open = false; | ||||||
| </script> | </script> | ||||||
| @@ -33,7 +33,6 @@ | |||||||
|       </button> |       </button> | ||||||
|     {/if} |     {/if} | ||||||
|   </span> |   </span> | ||||||
|   <p class="mb-8 text-lg text-gray-500">manage runner organizations</p> |  | ||||||
|   <OrgOverview bind:current_organizations /> |   <OrgOverview bind:current_organizations /> | ||||||
| </section> | </section> | ||||||
| 
 | 
 | ||||||
| @@ -9,8 +9,9 @@ | |||||||
| <div class="text-center items-center justify-center"> | <div class="text-center items-center justify-center"> | ||||||
|   <p class="mb-16 text-lg text-gray-500"> |   <p class="mb-16 text-lg text-gray-500"> | ||||||
|     <img class="w-full h-44" src={org_empty} alt="" /> |     <img class="w-full h-44" src={org_empty} alt="" /> | ||||||
|     <span class="font-bold">There are no organizations added yet.</span><br /> |     <span | ||||||
|     <span>Add your first organization</span> |       class="font-bold">{$_('there-are-no-organizations-added-yet')}</span><br /> | ||||||
|  |     <span>{$_('add-your-first-organization')}</span> | ||||||
|   </p> |   </p> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB | 
| @@ -28,7 +28,7 @@ | |||||||
|                 Role |                 Role | ||||||
|               </th> |               </th> | ||||||
|               <th scope="col" class="relative px-6 py-3"> |               <th scope="col" class="relative px-6 py-3"> | ||||||
|                 <span class="sr-only">Edit</span> |                 <span class="sr-only">{$_('edit')}</span> | ||||||
|               </th> |               </th> | ||||||
|             </tr> |             </tr> | ||||||
|           </thead> |           </thead> | ||||||
| @@ -71,7 +71,7 @@ | |||||||
|                 class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> |                 class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|                 <a |                 <a | ||||||
|                   href="#" |                   href="#" | ||||||
|                   class="text-indigo-600 hover:text-indigo-900">Edit</a> |                   class="text-indigo-600 hover:text-indigo-900">{$_('edit')}</a> | ||||||
|               </td> |               </td> | ||||||
|             </tr> |             </tr> | ||||||
| 
 | 
 | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   import { clickOutside } from "./outsideclick"; |   import { clickOutside } from "../base/outsideclick"; | ||||||
|   import { focusTrap } from "svelte-focus-trap"; |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|   import { |   import { | ||||||
|     RunnerService, |     RunnerService, | ||||||
| @@ -10,6 +10,7 @@ | |||||||
|   import isEmail from "validator/es/lib/isEmail"; |   import isEmail from "validator/es/lib/isEmail"; | ||||||
|   import isMobilePhone from "validator/es/lib/isMobilePhone"; |   import isMobilePhone from "validator/es/lib/isMobilePhone"; | ||||||
|   import Toastify from "toastify-js"; |   import Toastify from "toastify-js"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|   export let modal_open; |   export let modal_open; | ||||||
|   export let current_runners; |   export let current_runners; | ||||||
|   $: selected_team = undefined; |   $: selected_team = undefined; | ||||||
| @@ -20,11 +21,15 @@ | |||||||
|   let email_input; |   let email_input; | ||||||
|   let teams = []; |   let teams = []; | ||||||
|   RunnerTeamService.runnerTeamControllerGetAll().then((val) => { |   RunnerTeamService.runnerTeamControllerGetAll().then((val) => { | ||||||
|     teams = val; |     teams = val.map((r) => { | ||||||
|  |       return { label: `${r.parentGroup.name} > ${r.name}`, value: r }; | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
|   let orgs = []; |   let orgs = []; | ||||||
|   RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { |   RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { | ||||||
|     orgs = val; |     orgs = val.map((r) => { | ||||||
|  |       return { label: r.name, value: r }; | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
|   function focus(el) { |   function focus(el) { | ||||||
|     el.focus(); |     el.focus(); | ||||||
| @@ -36,13 +41,15 @@ | |||||||
|   $: firstname_input_value = ""; |   $: firstname_input_value = ""; | ||||||
|   $: processed_last_submit = true; |   $: processed_last_submit = true; | ||||||
|   $: isPhoneValidOrEmpty = |   $: isPhoneValidOrEmpty = | ||||||
|     isMobilePhone( |     (phone_input_value.includes("+") && | ||||||
|       phone_input_value |       isMobilePhone( | ||||||
|         .replaceAll("(", "") |         phone_input_value | ||||||
|         .replaceAll(")", "") |           .replaceAll("(", "") | ||||||
|         .replaceAll("-", "") |           .replaceAll(")", "") | ||||||
|         .replaceAll(" ", "") |           .replaceAll("-", "") | ||||||
|     ) || phone_input_value === ""; |           .replaceAll(" ", "") | ||||||
|  |       )) || | ||||||
|  |     phone_input_value === ""; | ||||||
|   $: isEmailValidOrEmpty = |   $: isEmailValidOrEmpty = | ||||||
|     isEmail(email_input_value) || email_input_value === ""; |     isEmail(email_input_value) || email_input_value === ""; | ||||||
|   $: isLastnameValid = lastname_input_value.trim().length !== 0; |   $: isLastnameValid = lastname_input_value.trim().length !== 0; | ||||||
| @@ -70,7 +77,7 @@ | |||||||
|     if (processed_last_submit === true) { |     if (processed_last_submit === true) { | ||||||
|       processed_last_submit = false; |       processed_last_submit = false; | ||||||
|       const toast = Toastify({ |       const toast = Toastify({ | ||||||
|         text: "Runner is being added...", |         text: $_("runner-is-being-added"), | ||||||
|         duration: -1, |         duration: -1, | ||||||
|       }).showToast(); |       }).showToast(); | ||||||
|       let postdata = { |       let postdata = { | ||||||
| @@ -96,7 +103,7 @@ | |||||||
|           modal_open = false; |           modal_open = false; | ||||||
|           // |           // | ||||||
|           Toastify({ |           Toastify({ | ||||||
|             text: "Runner added", |             text: $_("runner-added"), | ||||||
|             duration: 500, |             duration: 500, | ||||||
|             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|           }).showToast(); |           }).showToast(); | ||||||
| @@ -134,7 +141,7 @@ | |||||||
|         class="hidden sm:inline-block sm:align-middle sm:h-screen" |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|         aria-hidden="true">​</span> |         aria-hidden="true">​</span> | ||||||
|       <div |       <div | ||||||
|         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" |         class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|         role="dialog" |         role="dialog" | ||||||
|         aria-modal="true" |         aria-modal="true" | ||||||
|         aria-labelledby="modal-headline"> |         aria-labelledby="modal-headline"> | ||||||
| @@ -201,10 +208,10 @@ | |||||||
|                 <div class="col-span-6"> |                 <div class="col-span-6"> | ||||||
|                   <label |                   <label | ||||||
|                     for="lastname" |                     for="lastname" | ||||||
|                     class="block text-sm font-medium text-gray-700">Last Name</label> |                     class="block text-sm font-medium text-gray-700">{$_('last-name')}</label> | ||||||
|                   <input |                   <input | ||||||
|                     autocomplete="off" |                     autocomplete="off" | ||||||
|                     placeholder="Last Name" |                     placeholder={$_('last-name')} | ||||||
|                     class:border-red-500={!isLastnameValid} |                     class:border-red-500={!isLastnameValid} | ||||||
|                     class:focus:border-red-500={!isLastnameValid} |                     class:focus:border-red-500={!isLastnameValid} | ||||||
|                     class:focus:ring-red-500={!isLastnameValid} |                     class:focus:ring-red-500={!isLastnameValid} | ||||||
| @@ -224,29 +231,29 @@ | |||||||
|                   <label |                   <label | ||||||
|                     for="team" |                     for="team" | ||||||
|                     class="block text-sm font-medium text-gray-700">{$_('team')}</label> |                     class="block text-sm font-medium text-gray-700">{$_('team')}</label> | ||||||
|                   <select |                   <Select | ||||||
|                     name="team" |                     containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|                     bind:value={selected_team} |                     itemFilter={(label, filterText, option) => label | ||||||
|                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"> |                         .toLowerCase() | ||||||
|                     {#each teams as team} |                         .includes( | ||||||
|                       <option value={team.id}> |                           filterText.toLowerCase() | ||||||
|                         {team.parentGroup.name} |                         ) || option.value.id | ||||||
|                         > |                         .toString() | ||||||
|                         {team.name} |                         .startsWith(filterText.toLowerCase())} | ||||||
|                       </option> |                     items={orgs.concat(teams)} | ||||||
|                     {/each} |                     showChevron={true} | ||||||
|                     {#each orgs as org} |                     placeholder={$_('search-for-an-organization-or-team-by-name-or-id')} | ||||||
|                       <option value={org.id}>{org.name}</option> |                     noOptionsMessage={$_('no-organization-or-team-found')} | ||||||
|                     {/each} |                     on:select={(selectedValue) => (selected_team = selectedValue.detail.value.id)} | ||||||
|                   </select> |                     on:clear={() => (selected_team = null)} /> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="col-span-6"> |                 <div class="col-span-6"> | ||||||
|                   <label |                   <label | ||||||
|                     for="phone" |                     for="phone" | ||||||
|                     class="block text-sm font-medium text-gray-700">Phone</label> |                     class="block text-sm font-medium text-gray-700">{$_('phone')}</label> | ||||||
|                   <input |                   <input | ||||||
|                     autocomplete="off" |                     autocomplete="off" | ||||||
|                     placeholder="Phone" |                     placeholder={$_('phone')} | ||||||
|                     class:border-red-500={!isPhoneValidOrEmpty} |                     class:border-red-500={!isPhoneValidOrEmpty} | ||||||
|                     class:focus:border-red-500={!isPhoneValidOrEmpty} |                     class:focus:border-red-500={!isPhoneValidOrEmpty} | ||||||
|                     class:focus:ring-red-500={!isPhoneValidOrEmpty} |                     class:focus:ring-red-500={!isPhoneValidOrEmpty} | ||||||
| @@ -258,7 +265,7 @@ | |||||||
|                   {#if !isPhoneValidOrEmpty} |                   {#if !isPhoneValidOrEmpty} | ||||||
|                     <span |                     <span | ||||||
|                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|                       {$_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')} |                       {@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')} | ||||||
|                     </span> |                     </span> | ||||||
|                   {/if} |                   {/if} | ||||||
|                 </div> |                 </div> | ||||||
| @@ -2,7 +2,7 @@ | |||||||
|   import csv from "csvtojson"; |   import csv from "csvtojson"; | ||||||
|   import { read as readXlsx, utils as xlsx_utils } from "xlsx"; |   import { read as readXlsx, utils as xlsx_utils } from "xlsx"; | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   import { clickOutside } from "./outsideclick"; |   import { clickOutside } from "../base/outsideclick"; | ||||||
|   import { focusTrap } from "svelte-focus-trap"; |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|   import Toastify from "toastify-js"; |   import Toastify from "toastify-js"; | ||||||
|   import { |   import { | ||||||
| @@ -11,6 +11,7 @@ | |||||||
|     RunnerOrganizationService, |     RunnerOrganizationService, | ||||||
|   } from "@odit/lfk-client-js"; |   } from "@odit/lfk-client-js"; | ||||||
|   import { createEventDispatcher } from "svelte"; |   import { createEventDispatcher } from "svelte"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|   export let opened_from; |   export let opened_from; | ||||||
|   export let passed_org; |   export let passed_org; | ||||||
|   export let passed_orgs; |   export let passed_orgs; | ||||||
| @@ -20,6 +21,7 @@ | |||||||
|   $: searchvalue = ""; |   $: searchvalue = ""; | ||||||
|   const dispatch = createEventDispatcher(); |   const dispatch = createEventDispatcher(); | ||||||
|   function cancelModal() { |   function cancelModal() { | ||||||
|  |     json_output = []; | ||||||
|     import_modal_open = false; |     import_modal_open = false; | ||||||
|     dispatch("cancel"); |     dispatch("cancel"); | ||||||
|   } |   } | ||||||
| @@ -34,13 +36,18 @@ | |||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
|   })(); |   })(); | ||||||
|   let orgs = []; |   let groups = []; | ||||||
|   RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { |   RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { | ||||||
|     orgs = val; |     const orgs = val.map((r) => { | ||||||
|   }); |       return { label: r.name, value: `ORG_${r.id}` }; | ||||||
|   let teams = []; |     }); | ||||||
|   RunnerTeamService.runnerTeamControllerGetAll().then((val) => { |     groups = groups.concat(orgs); | ||||||
|     teams = val; |     RunnerTeamService.runnerTeamControllerGetAll().then((val) => { | ||||||
|  |       const teams = val.map((r) => { | ||||||
|  |         return { label: `${r.parentGroup.name} > ${r.name}`, value: `TEAM_${r.id}` }; | ||||||
|  |       }); | ||||||
|  |       groups = groups.concat(teams); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
|   let selected_org; |   let selected_org; | ||||||
|   $: selected_org_or_team = ""; |   $: selected_org_or_team = ""; | ||||||
| @@ -77,7 +84,7 @@ | |||||||
|   function importAction() { |   function importAction() { | ||||||
|     if (recent_processed === true) { |     if (recent_processed === true) { | ||||||
|       const toast = Toastify({ |       const toast = Toastify({ | ||||||
|         text: "Runners are being imported...", |         text: $_("runners-are-being-imported"), | ||||||
|         duration: -1, |         duration: -1, | ||||||
|       }).showToast(); |       }).showToast(); | ||||||
|       recent_processed = false; |       recent_processed = false; | ||||||
| @@ -104,7 +111,7 @@ | |||||||
|             toast.hideToast(); |             toast.hideToast(); | ||||||
|             recent_processed = true; |             recent_processed = true; | ||||||
|             Toastify({ |             Toastify({ | ||||||
|               text: "Import finished", |               text: $_("import-finished"), | ||||||
|               duration: 500, |               duration: 500, | ||||||
|               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", |               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|             }).showToast(); |             }).showToast(); | ||||||
| @@ -121,7 +128,7 @@ | |||||||
|             toast.hideToast(); |             toast.hideToast(); | ||||||
|             recent_processed = true; |             recent_processed = true; | ||||||
|             Toastify({ |             Toastify({ | ||||||
|               text: "Import finished", |               text: $_("import-finished"), | ||||||
|               duration: 500, |               duration: 500, | ||||||
|               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", |               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|             }).showToast(); |             }).showToast(); | ||||||
| @@ -144,7 +151,7 @@ | |||||||
|               toast.hideToast(); |               toast.hideToast(); | ||||||
|               recent_processed = true; |               recent_processed = true; | ||||||
|               Toastify({ |               Toastify({ | ||||||
|                 text: "Import finished", |                 text: $_("import-finished"), | ||||||
|                 duration: 500, |                 duration: 500, | ||||||
|                 backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", |                 backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|               }).showToast(); |               }).showToast(); | ||||||
| @@ -201,7 +208,7 @@ | |||||||
|         class="hidden sm:inline-block sm:align-middle sm:h-screen" |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|         aria-hidden="true">​</span> |         aria-hidden="true">​</span> | ||||||
|       <div |       <div | ||||||
|         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" |         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-max sm:w-full" | ||||||
|         role="dialog" |         role="dialog" | ||||||
|         aria-modal="true" |         aria-modal="true" | ||||||
|         aria-labelledby="modal-headline"> |         aria-labelledby="modal-headline"> | ||||||
| @@ -253,22 +260,24 @@ | |||||||
|                 <p>{$_('bitte-bestaetige-diese-laeufer-fuer-den-import')}</p> |                 <p>{$_('bitte-bestaetige-diese-laeufer-fuer-den-import')}</p> | ||||||
|               {/if} |               {/if} | ||||||
|               {#if opened_from === 'RunnerOverview'} |               {#if opened_from === 'RunnerOverview'} | ||||||
|                 <p>Group</p> |                 <p>{$_('group')}</p> | ||||||
|                 <select |                 <Select | ||||||
|                   name="team" |                   containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|                   bind:value={selected_org_or_team} |                   itemFilter={(label, filterText, option) => label | ||||||
|                   class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"> |                       .toLowerCase() | ||||||
|                   {#each teams as team} |                       .includes( | ||||||
|                     <option value="TEAM_{team.id}"> |                         filterText.toLowerCase() | ||||||
|                       {team.parentGroup.name} |                       ) || option.id.value | ||||||
|                       > |                       .toString() | ||||||
|                       {team.name} |                       .startsWith(filterText.toLowerCase())} | ||||||
|                     </option> |                   items={groups} | ||||||
|                   {/each} |                   showChevron={true} | ||||||
|                   {#each orgs as org} |                   placeholder={$_('search-for-an-organization-or-team-by-name-or-id')} | ||||||
|                     <option value="ORG_{org.id}">{org.name}</option> |                   noOptionsMessage={$_('no-organization-or-team-found')} | ||||||
|                   {/each} |                   on:select={(selectedValue) => { | ||||||
|                 </select> |                     selected_org_or_team = selectedValue.detail.value; | ||||||
|  |                   }} | ||||||
|  |                   on:clear={() => (selected_org_or_team = null)} /> | ||||||
|               {/if} |               {/if} | ||||||
|               {#if opened_from === 'OrgDetail'} |               {#if opened_from === 'OrgDetail'} | ||||||
|                 <p> |                 <p> | ||||||
| @@ -347,7 +356,6 @@ | |||||||
|                 </button> |                 </button> | ||||||
|                 <button |                 <button | ||||||
|                   on:click={() => { |                   on:click={() => { | ||||||
|                     json_output = []; |  | ||||||
|                     cancelModal(); |                     cancelModal(); | ||||||
|                   }} |                   }} | ||||||
|                   type="button" |                   type="button" | ||||||
| @@ -1,54 +1,78 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { getLocaleFromNavigator, _ } from "svelte-i18n"; | ||||||
|   import lodashIsEqual from "lodash.isequal"; |   import store from "../../store"; | ||||||
|   import store from "../store"; |  | ||||||
|   import { |   import { | ||||||
|     RunnerService, |     RunnerService, | ||||||
|     RunnerTeamService, |     RunnerTeamService, | ||||||
|     RunnerOrganizationService, |     RunnerOrganizationService, | ||||||
|   } from "@odit/lfk-client-js"; |   } from "@odit/lfk-client-js"; | ||||||
|   import Toastify from "toastify-js"; |   import Toastify from "toastify-js"; | ||||||
|   import PromiseError from "./PromiseError.svelte"; |   import PromiseError from "../base/PromiseError.svelte"; | ||||||
|   import isEmail from "validator/es/lib/isEmail"; |   import isEmail from "validator/es/lib/isEmail"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|   let data_loaded = false; |   let data_loaded = false; | ||||||
|   export let params; |   export let params; | ||||||
|   const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid); |   const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid); | ||||||
|   $: delete_triggered = false; |   $: delete_triggered = false; | ||||||
|  |   $: sponsoring_contracts_download_open = false; | ||||||
|  |   $: original_data_pdf = {}; | ||||||
|   $: original_data = {}; |   $: original_data = {}; | ||||||
|   $: editable = {}; |   $: editable = {}; | ||||||
|   $: changes_performed = !lodashIsEqual(original_data, editable); |   $: group = {} | ||||||
|  |   $: changes_performed = !(JSON.stringify(original_data) == JSON.stringify(editable)); | ||||||
|   $: isEmailValid = |   $: isEmailValid = | ||||||
|     (editable.email || "") === "" || |     (editable.email || "") === "" || | ||||||
|     (editable.email && isEmail(editable.email || "")); |     (editable.email && isEmail(editable.email || "")); | ||||||
|   $: isFirstnameValid = editable.firstname !== ""; |   $: isFirstnameValid = editable.firstname !== ""; | ||||||
|   $: isLastnameValid = editable.lastname !== ""; |   $: isLastnameValid = editable.lastname !== ""; | ||||||
|   $: save_enabled = |   $: save_enabled = | ||||||
|     changes_performed && isFirstnameValid && isLastnameValid && isEmailValid; |     changes_performed && | ||||||
|  |     isFirstnameValid && | ||||||
|  |     isLastnameValid && | ||||||
|  |     isEmailValid && | ||||||
|  |     editable.group != null; | ||||||
|   runner_promise.then((data) => { |   runner_promise.then((data) => { | ||||||
|     data_loaded = true; |     data_loaded = true; | ||||||
|  |     original_data_pdf = Object.assign(original_data_pdf, data); | ||||||
|  |     data.group = data.group.id; | ||||||
|     original_data = Object.assign(original_data, data); |     original_data = Object.assign(original_data, data); | ||||||
|     original_data.group = original_data.group.id; |  | ||||||
|     editable = Object.assign(editable, original_data); |     editable = Object.assign(editable, original_data); | ||||||
|  | 
 | ||||||
|  |     RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { | ||||||
|  |       const orgs = val.map((r) => { | ||||||
|  |         return { label: r.name, value: r }; | ||||||
|  |       }); | ||||||
|  |       groups = groups.concat(orgs); | ||||||
|  |       RunnerTeamService.runnerTeamControllerGetAll().then((val) => { | ||||||
|  |         const teams = val.map((r) => { | ||||||
|  |           return { label: `${r.parentGroup.name} > ${r.name}`, value: r }; | ||||||
|  |         }); | ||||||
|  |         groups = groups.concat(teams); | ||||||
|  |         group = groups.find(g => g.value.id == editable.group) | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
|   let orgs = []; |   document.addEventListener("click", function (e) { | ||||||
|   RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { |     if ( | ||||||
|     orgs = val; |       e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && | ||||||
|   }); |       e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" | ||||||
|   let teams = []; |     ) { | ||||||
|   RunnerTeamService.runnerTeamControllerGetAll().then((val) => { |       sponsoring_contracts_download_open = false; | ||||||
|     teams = val; |     } | ||||||
|   }); |   }); | ||||||
|  |   let groups = []; | ||||||
|   function submit() { |   function submit() { | ||||||
|     if (data_loaded === true && save_enabled) { |     if (data_loaded === true && save_enabled) { | ||||||
|       Toastify({ |       Toastify({ | ||||||
|         text: $_("updating-runner"), |         text: $_("updating-runner"), | ||||||
|         duration: 2500, |         duration: 2500, | ||||||
|       }).showToast(); |       }).showToast(); | ||||||
|       RunnerService.runnerControllerPut(original_data.id, editable) |       let postdata = {}; | ||||||
|  |       postdata = Object.assign(postdata, editable); | ||||||
|  |       RunnerService.runnerControllerPut(original_data.id, postdata) | ||||||
|         .then((resp) => { |         .then((resp) => { | ||||||
|           Object.assign(original_data, editable); |           Object.assign(original_data, editable); | ||||||
|           original_data = editable; |           original_data = original_data; | ||||||
|           Object.assign(original_data, editable); |  | ||||||
|           Toastify({ |           Toastify({ | ||||||
|             text: $_("runner-updated"), |             text: $_("runner-updated"), | ||||||
|             duration: 2500, |             duration: 2500, | ||||||
| @@ -66,6 +90,59 @@ | |||||||
|       }) |       }) | ||||||
|       .catch((err) => {}); |       .catch((err) => {}); | ||||||
|   } |   } | ||||||
|  |   function generateSponsoringContract(locale) { | ||||||
|  |     sponsoring_contracts_download_open = false; | ||||||
|  |     const toast = Toastify({ | ||||||
|  |       text: $_("generating-pdf"), | ||||||
|  |       duration: -1, | ||||||
|  |     }).showToast(); | ||||||
|  |     fetch( | ||||||
|  |       `${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, | ||||||
|  |       { | ||||||
|  |         method: "POST", | ||||||
|  |         headers: { | ||||||
|  |           "Content-Type": "application/json", | ||||||
|  |         }, | ||||||
|  |         body: JSON.stringify([original_data_pdf]), | ||||||
|  |       } | ||||||
|  |     ) | ||||||
|  |       .then((response) => { | ||||||
|  |         if (response.status != "200") { | ||||||
|  |           toast.hideToast(); | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_("pdf-generation-failed"), | ||||||
|  |             duration: 3500, | ||||||
|  |             backgroundColor: | ||||||
|  |               "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", | ||||||
|  |           }).showToast(); | ||||||
|  |         } else { | ||||||
|  |           return response.blob(); | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |       .then((blob) => { | ||||||
|  |         const url = window.URL.createObjectURL(blob); | ||||||
|  |         let a = document.createElement("a"); | ||||||
|  |         a.href = url; | ||||||
|  |         a.download = | ||||||
|  |           "Sponsoring_" + | ||||||
|  |           original_data.firstname + | ||||||
|  |           (original_data.middlename || "") + | ||||||
|  |           original_data.lastname + | ||||||
|  |           ".pdf"; | ||||||
|  |         document.body.appendChild(a); | ||||||
|  |         a.click(); | ||||||
|  |         a.remove(); | ||||||
|  |         toast.hideToast(); | ||||||
|  |         Toastify({ | ||||||
|  |           text: $_("pdf-successfully-generated"), | ||||||
|  |           duration: 3500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => { | ||||||
|  |         console.error(err); | ||||||
|  |       }); | ||||||
|  |   } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#await runner_promise} | {#await runner_promise} | ||||||
| @@ -130,6 +207,64 @@ | |||||||
|               }} |               }} | ||||||
|               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> | ||||||
|           {/if} |           {/if} | ||||||
|  |           <div id="sponsoring:dropdown" class="relative inline-block"> | ||||||
|  |             <div> | ||||||
|  |               <button | ||||||
|  |                 on:click={() => { | ||||||
|  |                   sponsoring_contracts_download_open = !sponsoring_contracts_download_open; | ||||||
|  |                 }} | ||||||
|  |                 type="button" | ||||||
|  |                 class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex" | ||||||
|  |                 id="options-menu" | ||||||
|  |                 aria-haspopup="true" | ||||||
|  |                 aria-expanded="true"> | ||||||
|  |                 {$_('generate-sponsoring-contract')} | ||||||
|  |                 <svg | ||||||
|  |                   xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                   width="24" | ||||||
|  |                   height="24" | ||||||
|  |                   viewBox="0 0 24 24" | ||||||
|  |                   class="-mr-1 ml-2 h-5 w-5"><path | ||||||
|  |                     fill="none" | ||||||
|  |                     d="M0 0h24v24H0z" /> | ||||||
|  |                   <path | ||||||
|  |                     fill="currentColor" | ||||||
|  |                     d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg> | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |             {#if sponsoring_contracts_download_open} | ||||||
|  |               <div | ||||||
|  |                 class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5" | ||||||
|  |                 id="sponsoring:dropdown:menu"> | ||||||
|  |                 <div | ||||||
|  |                   class="py-1" | ||||||
|  |                   role="menu" | ||||||
|  |                   aria-orientation="vertical" | ||||||
|  |                   aria-labelledby="options-menu"> | ||||||
|  |                   <span | ||||||
|  |                     class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> | ||||||
|  |                   <button | ||||||
|  |                     on:click={() => { | ||||||
|  |                       generateSponsoringContract('de'); | ||||||
|  |                     }} | ||||||
|  |                     type="submit" | ||||||
|  |                     class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" | ||||||
|  |                     role="menuitem"> | ||||||
|  |                     {$_('german')} | ||||||
|  |                   </button> | ||||||
|  |                   <button | ||||||
|  |                     on:click={() => { | ||||||
|  |                       generateSponsoringContract('en'); | ||||||
|  |                     }} | ||||||
|  |                     type="submit" | ||||||
|  |                     class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" | ||||||
|  |                     role="menuitem"> | ||||||
|  |                     {$_('english')} | ||||||
|  |                   </button> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             {/if} | ||||||
|  |           </div> | ||||||
|           {#if !delete_triggered} |           {#if !delete_triggered} | ||||||
|             <button |             <button | ||||||
|               on:click={() => { |               on:click={() => { | ||||||
| @@ -237,21 +372,20 @@ | |||||||
|     </div> |     </div> | ||||||
|     <div class="text-sm w-full"> |     <div class="text-sm w-full"> | ||||||
|       <span class="font-medium text-gray-700">{$_('group')}</span> |       <span class="font-medium text-gray-700">{$_('group')}</span> | ||||||
|       <select |       <Select | ||||||
|         bind:value={editable.group} |         containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|         name="team" |         itemFilter={(label, filterText, option) => label | ||||||
|         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"> |             .toLowerCase() | ||||||
|         {#each teams as team} |             .includes( | ||||||
|           <option value={team.id}> |               filterText.toLowerCase() | ||||||
|             {team.parentGroup.name} |             ) || option.id.value.toString().startsWith(filterText.toLowerCase())} | ||||||
|             > |         items={groups} | ||||||
|             {team.name} |         showChevron={true} | ||||||
|           </option> |         placeholder={$_('search-for-an-organization-or-team-by-name-or-id')} | ||||||
|         {/each} |         noOptionsMessage={$_('no-organization-or-team-found')} | ||||||
|         {#each orgs as org} |         bind:selectedValue={group} | ||||||
|           <option value={org.id}>{org.name}</option> |         on:select={(selectedValue) => {editable.group = selectedValue.detail.value.id}} | ||||||
|         {/each} |         on:clear={() => (editable.group = null)} /> | ||||||
|       </select> |  | ||||||
|     </div> |     </div> | ||||||
|     <div class="text-sm w-full"> |     <div class="text-sm w-full"> | ||||||
|       <span class="font-medium text-gray-700">{$_('distance')}</span> |       <span class="font-medium text-gray-700">{$_('distance')}</span> | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   import store from "../store"; |   import store from "../../store"; | ||||||
|   import AddRunnerModal from "./AddRunnerModal.svelte"; |   import AddRunnerModal from "./AddRunnerModal.svelte"; | ||||||
|   import ImportRunnerModal from "./ImportRunnerModal.svelte"; |   import ImportRunnerModal from "./ImportRunnerModal.svelte"; | ||||||
|   import RunnersOverview from "./RunnersOverview.svelte"; |   import RunnersOverview from "./RunnersOverview.svelte"; | ||||||
| @@ -19,7 +19,7 @@ | |||||||
|         }} |         }} | ||||||
|         type="button" |         type="button" | ||||||
|         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> |         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|         Läufer hinzufügen |         {$_('laeufer-hinzufuegen')} | ||||||
|       </button> |       </button> | ||||||
|       <button |       <button | ||||||
|         on:click={() => { |         on:click={() => { | ||||||
| @@ -27,7 +27,7 @@ | |||||||
|         }} |         }} | ||||||
|         type="button" |         type="button" | ||||||
|         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> |         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|         Läufer importieren |         {$_('import-runners')} | ||||||
|       </button> |       </button> | ||||||
|     {/if} |     {/if} | ||||||
|   </span> |   </span> | ||||||
| @@ -1,16 +1,12 @@ | |||||||
| <script> | <script> | ||||||
|   import { _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   // import AddUserModal from "./AddUserModal.svelte"; |  | ||||||
|   import runners_empty from "./runners_empty.svg"; |   import runners_empty from "./runners_empty.svg"; | ||||||
|   // let modal_open = false; |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="text-center items-center justify-center"> | <div class="text-center items-center justify-center"> | ||||||
|   <p class="mb-16 text-lg text-gray-500"> |   <p class="mb-16 text-lg text-gray-500"> | ||||||
|     <img class="w-full h-44" src={runners_empty} alt="" /> |     <img class="w-full h-44" src={runners_empty} alt="" /> | ||||||
|     <span class="font-bold">There are no runners added yet.</span><br /> |     <span class="font-bold">{$_('there-are-no-runners-added-yet')}</span><br /> | ||||||
|     <span>Add your first runner</span> |     <span>{$_('add-your-first-runner')}</span> | ||||||
|   </p> |   </p> | ||||||
| </div> | </div> | ||||||
| 
 |  | ||||||
| <!-- <AddUserModal bind:modal_open /> --> |  | ||||||
							
								
								
									
										364
									
								
								src/components/runners/RunnersOverview.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,364 @@ | |||||||
|  | <script> | ||||||
|  |   import { getLocaleFromNavigator, _ } from "svelte-i18n"; | ||||||
|  |   import { | ||||||
|  |     RunnerService, | ||||||
|  |     RunnerTeamService, | ||||||
|  |     RunnerOrganizationService, | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import RunnersEmptyState from "./RunnersEmptyState.svelte"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   $: searchvalue = ""; | ||||||
|  |   $: active_deletes = []; | ||||||
|  |   export let current_runners = []; | ||||||
|  |   const runners_promise = RunnerService.runnerControllerGetAll().then((val) => { | ||||||
|  |     current_runners = val; | ||||||
|  |   }); | ||||||
|  |   $: selectedFilter_teams = null; | ||||||
|  |   $: selectedFilter = null; | ||||||
|  |   $: filter__teams = selectedFilter_teams || []; | ||||||
|  |   $: filter__orgs = selectedFilter || []; | ||||||
|  |   $: filterGroupIDs = filter__teams.concat(filter__orgs).map((i) => i.value); | ||||||
|  |   $: sponsoring_contracts_download_open = false; | ||||||
|  |   $: teams = []; | ||||||
|  |   $: orgs = []; | ||||||
|  |   $: mappedteams = teams.map(function (g) { | ||||||
|  |     return { value: g.id, label: g.parentGroup.name + " > " + g.name }; | ||||||
|  |   }); | ||||||
|  |   $: selectgroups = orgs | ||||||
|  |     .map(function (g) { | ||||||
|  |       return { value: g.id, label: g.name }; | ||||||
|  |     }) | ||||||
|  |     .concat(mappedteams); | ||||||
|  |   document.addEventListener("click", function (e) { | ||||||
|  |     if ( | ||||||
|  |       e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && | ||||||
|  |       e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" | ||||||
|  |     ) { | ||||||
|  |       sponsoring_contracts_download_open = false; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   RunnerTeamService.runnerTeamControllerGetAll().then((val) => { | ||||||
|  |     teams = val; | ||||||
|  |   }); | ||||||
|  |   RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { | ||||||
|  |     orgs = val; | ||||||
|  |   }); | ||||||
|  |   function should_display_based_on_id(id) { | ||||||
|  |     if (searchvalue.toString().slice(-1) === "*") { | ||||||
|  |       return id.toString().startsWith(searchvalue.replace("*", "")); | ||||||
|  |     } | ||||||
|  |     return id.toString() === searchvalue; | ||||||
|  |   } | ||||||
|  |   function generateSponsoringContract(locale) { | ||||||
|  |     sponsoring_contracts_download_open = false; | ||||||
|  |     const toast = Toastify({ | ||||||
|  |       text: $_("generating-pdf"), | ||||||
|  |       duration: -1, | ||||||
|  |     }).showToast(); | ||||||
|  |     fetch( | ||||||
|  |       `${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, | ||||||
|  |       { | ||||||
|  |         method: "POST", | ||||||
|  |         headers: { | ||||||
|  |           "Content-Type": "application/json", | ||||||
|  |         }, | ||||||
|  |         body: JSON.stringify( | ||||||
|  |           current_runners.filter((r) => r.is_selected === true) | ||||||
|  |         ), | ||||||
|  |       } | ||||||
|  |     ) | ||||||
|  |       .then((response) => { | ||||||
|  |         if (response.status != "200") { | ||||||
|  |           toast.hideToast(); | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_("pdf-generation-failed"), | ||||||
|  |             duration: 3500, | ||||||
|  |             backgroundColor: | ||||||
|  |               "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", | ||||||
|  |           }).showToast(); | ||||||
|  |         } else { | ||||||
|  |           return response.blob(); | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |       .then((blob) => { | ||||||
|  |         const url = window.URL.createObjectURL(blob); | ||||||
|  |         let a = document.createElement("a"); | ||||||
|  |         a.href = url; | ||||||
|  |         a.download = "Sponsoring.pdf"; | ||||||
|  |         document.body.appendChild(a); | ||||||
|  |         a.click(); | ||||||
|  |         a.remove(); | ||||||
|  |         toast.hideToast(); | ||||||
|  |         Toastify({ | ||||||
|  |           text: $_("pdf-successfully-generated"), | ||||||
|  |           duration: 3500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => { | ||||||
|  |         console.error(err); | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} | ||||||
|  |   {#await runners_promise} | ||||||
|  |     <div | ||||||
|  |       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||||
|  |       role="alert"> | ||||||
|  |       <p class="font-bold">{$_('runners-are-being-loaded')}</p> | ||||||
|  |       <p class="text-sm">{$_('this-might-take-a-moment')}</p> | ||||||
|  |     </div> | ||||||
|  |   {:then} | ||||||
|  |     {#if current_runners.length === 0} | ||||||
|  |       <RunnersEmptyState /> | ||||||
|  |     {:else} | ||||||
|  |       <input | ||||||
|  |         type="search" | ||||||
|  |         bind:value={searchvalue} | ||||||
|  |         placeholder={$_('datatable.search')} | ||||||
|  |         aria-label={$_('datatable.search')} | ||||||
|  |         class="gridjs-input gridjs-search-input mb-4" /> | ||||||
|  |       <div class="block mb-6"> | ||||||
|  |         <label | ||||||
|  |           for="country" | ||||||
|  |           class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label> | ||||||
|  |         <Select | ||||||
|  |           on:select={(event) => { | ||||||
|  |             selectedFilter = event.detail; | ||||||
|  |           }} | ||||||
|  |           selectedValue={selectedFilter} | ||||||
|  |           placeholder={$_('filter-by-organization-team')} | ||||||
|  |           containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" | ||||||
|  |           items={selectgroups} | ||||||
|  |           isMulti={true} /> | ||||||
|  |       </div> | ||||||
|  |       <div class="h-12"> | ||||||
|  |         {#if current_runners.some((r) => r.is_selected === true)} | ||||||
|  |           <div id="sponsoring:dropdown" class="relative inline-block"> | ||||||
|  |             <div> | ||||||
|  |               <button | ||||||
|  |                 on:click={() => { | ||||||
|  |                   sponsoring_contracts_download_open = !sponsoring_contracts_download_open; | ||||||
|  |                 }} | ||||||
|  |                 type="button" | ||||||
|  |                 class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex" | ||||||
|  |                 id="options-menu" | ||||||
|  |                 aria-haspopup="true" | ||||||
|  |                 aria-expanded="true"> | ||||||
|  |                 {$_('generate-sponsoring-contracts')} | ||||||
|  |                 <svg | ||||||
|  |                   xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                   width="24" | ||||||
|  |                   height="24" | ||||||
|  |                   viewBox="0 0 24 24" | ||||||
|  |                   class="-mr-1 ml-2 h-5 w-5"><path | ||||||
|  |                     fill="none" | ||||||
|  |                     d="M0 0h24v24H0z" /> | ||||||
|  |                   <path | ||||||
|  |                     fill="currentColor" | ||||||
|  |                     d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg> | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |             {#if sponsoring_contracts_download_open} | ||||||
|  |               <div | ||||||
|  |                 class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5" | ||||||
|  |                 id="sponsoring:dropdown:menu"> | ||||||
|  |                 <div | ||||||
|  |                   class="py-1" | ||||||
|  |                   role="menu" | ||||||
|  |                   aria-orientation="vertical" | ||||||
|  |                   aria-labelledby="options-menu"> | ||||||
|  |                   <span | ||||||
|  |                     class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> | ||||||
|  |                   <button | ||||||
|  |                     on:click={() => { | ||||||
|  |                       generateSponsoringContract('de'); | ||||||
|  |                     }} | ||||||
|  |                     type="submit" | ||||||
|  |                     class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" | ||||||
|  |                     role="menuitem"> | ||||||
|  |                     {$_('german')} | ||||||
|  |                   </button> | ||||||
|  |                   <button | ||||||
|  |                     on:click={() => { | ||||||
|  |                       generateSponsoringContract('en'); | ||||||
|  |                     }} | ||||||
|  |                     type="submit" | ||||||
|  |                     class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" | ||||||
|  |                     role="menuitem"> | ||||||
|  |                     {$_('english')} | ||||||
|  |                   </button> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             {/if} | ||||||
|  |           </div> | ||||||
|  |         {/if} | ||||||
|  |       </div> | ||||||
|  |       <div | ||||||
|  |         class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> | ||||||
|  |         <table class="divide-y divide-gray-200 w-full"> | ||||||
|  |           <thead class="bg-gray-50"> | ||||||
|  |             <tr> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 <span | ||||||
|  |                   on:click={() => { | ||||||
|  |                     const newstate = !current_runners.some((r) => r.is_selected === true); | ||||||
|  |                     current_runners = current_runners.map((r) => { | ||||||
|  |                       r.is_selected = newstate; | ||||||
|  |                       return r; | ||||||
|  |                     }); | ||||||
|  |                   }} | ||||||
|  |                   class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)} | ||||||
|  |                     {$_('deselect-all')} | ||||||
|  |                   {:else}{$_('select-all')}{/if} | ||||||
|  |                 </span> | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('name')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('contact-information')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('group')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('distance-in-km')} | ||||||
|  |               </th> | ||||||
|  |               <th scope="col" class="relative px-6 py-3"> | ||||||
|  |                 <span class="sr-only">{$_('action')}</span> | ||||||
|  |               </th> | ||||||
|  |             </tr> | ||||||
|  |           </thead> | ||||||
|  |           <tbody class="divide-y divide-gray-200"> | ||||||
|  |             {#each current_runners as runner} | ||||||
|  |               {#if runner.firstname | ||||||
|  |                 .toLowerCase() | ||||||
|  |                 .includes( | ||||||
|  |                   searchvalue.toLowerCase() | ||||||
|  |                 ) || runner.middlename | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || runner.lastname | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || should_display_based_on_id(runner.id)} | ||||||
|  |                 {#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0} | ||||||
|  |                   <tr | ||||||
|  |                     data-rowid="user_{runner.id}" | ||||||
|  |                     data-groupid={runner.group.id}> | ||||||
|  |                     <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                       <input | ||||||
|  |                         bind:checked={runner.is_selected} | ||||||
|  |                         type="checkbox" | ||||||
|  |                         class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |                     </td> | ||||||
|  |                     <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                       <div class="flex items-center"> | ||||||
|  |                         <div class="ml-4"> | ||||||
|  |                           <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                             {runner.firstname} | ||||||
|  |                             {runner.middlename || ''} | ||||||
|  |                             {runner.lastname} | ||||||
|  |                           </div> | ||||||
|  |                         </div> | ||||||
|  |                       </div> | ||||||
|  |                     </td> | ||||||
|  |                     <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                       {#if runner.email} | ||||||
|  |                         <div class="text-sm text-gray-500">{runner.email}</div> | ||||||
|  |                       {/if} | ||||||
|  |                       {#if runner.phone} | ||||||
|  |                         <div class="text-sm text-gray-500">{runner.phone}</div> | ||||||
|  |                       {/if} | ||||||
|  |                       {#if runner.address.address1 !== null} | ||||||
|  |                         {runner.address.address1}<br /> | ||||||
|  |                         {runner.address.address2 || ''}<br /> | ||||||
|  |                         {runner.address.postalcode} | ||||||
|  |                         {runner.address.city} | ||||||
|  |                         {runner.address.country} | ||||||
|  |                       {/if} | ||||||
|  |                     </td> | ||||||
|  |                     <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                       {#if runner.group.responseType === 'RUNNERTEAM'} | ||||||
|  |                         <a | ||||||
|  |                           href="../teams/{runner.group.id}" | ||||||
|  |                           class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a> | ||||||
|  |                       {/if} | ||||||
|  |                       {#if runner.group.responseType === 'RUNNERORGANIZATION'} | ||||||
|  |                         <a | ||||||
|  |                           href="../orgs/{runner.group.id}" | ||||||
|  |                           class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a> | ||||||
|  |                       {/if} | ||||||
|  |                     </td> | ||||||
|  |                     <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                       {runner.distance} | ||||||
|  |                     </td> | ||||||
|  |                     {#if active_deletes[runner.id] === true} | ||||||
|  |                       <td | ||||||
|  |                         class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                         <button | ||||||
|  |                           on:click={() => { | ||||||
|  |                             active_deletes[runner.id] = false; | ||||||
|  |                           }} | ||||||
|  |                           tabindex="0" | ||||||
|  |                           class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> | ||||||
|  |                         <button | ||||||
|  |                           on:click={() => { | ||||||
|  |                             RunnerService.runnerControllerRemove(runner.id, true) | ||||||
|  |                               .then((resp) => { | ||||||
|  |                                 current_runners = current_runners.filter((obj) => obj.id !== runner.id); | ||||||
|  |                               }) | ||||||
|  |                               .catch((err) => {}); | ||||||
|  |                           }} | ||||||
|  |                           tabindex="0" | ||||||
|  |                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> | ||||||
|  |                       </td> | ||||||
|  |                     {:else} | ||||||
|  |                       <td | ||||||
|  |                         class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                         <a | ||||||
|  |                           href="./{runner.id}" | ||||||
|  |                           class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> | ||||||
|  |                         {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')} | ||||||
|  |                           <button | ||||||
|  |                             on:click={() => { | ||||||
|  |                               active_deletes[runner.id] = true; | ||||||
|  |                             }} | ||||||
|  |                             tabindex="0" | ||||||
|  |                             class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> | ||||||
|  |                         {/if} | ||||||
|  |                       </td> | ||||||
|  |                     {/if} | ||||||
|  |                   </tr> | ||||||
|  |                 {/if} | ||||||
|  |               {/if} | ||||||
|  |             {/each} | ||||||
|  |           </tbody> | ||||||
|  |         </table> | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |   {:catch error} | ||||||
|  |     <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> | ||||||
|  |       <span class="inline-block align-middle mr-8"> | ||||||
|  |         <b class="capitalize">{$_('general_promise_error')}</b> | ||||||
|  |         {error} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |   {/await} | ||||||
|  | {/if} | ||||||
| Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB | 
							
								
								
									
										195
									
								
								src/components/scans/AddScanModal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,195 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { clickOutside } from "../base/outsideclick"; | ||||||
|  |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|  |   import { | ||||||
|  |     RunnerService, | ||||||
|  |     ScanService, | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   export let modal_open; | ||||||
|  |   export let current_scans; | ||||||
|  |   const getRunnerLabel = (option) => | ||||||
|  |     option.firstname + " " + (option.middlename || "") + " " + option.lastname; | ||||||
|  |   const filterRunners = (label, filterText, option) => | ||||||
|  |     label.toLowerCase().includes(filterText.toLowerCase()) || | ||||||
|  |     option.value.toString().startsWith(filterText.toLowerCase()); | ||||||
|  |   function focus(el) { | ||||||
|  |     el.focus(); | ||||||
|  |   } | ||||||
|  |   $: runner = 0; | ||||||
|  |   $: runners = []; | ||||||
|  |   RunnerService.runnerControllerGetAll().then((val) => { | ||||||
|  |     runners = val.map(r => {return {label: getRunnerLabel(r), value: r}}); | ||||||
|  |   }); | ||||||
|  |   $: distance_input = 0; | ||||||
|  |   $: processed_last_submit = true; | ||||||
|  |   $: is_distance_valid = distance_input > 0; | ||||||
|  |   $: createbtnenabled = is_distance_valid; | ||||||
|  |   (() => { | ||||||
|  |     document.onkeydown = (e) => { | ||||||
|  |       e = e || window.event; | ||||||
|  |       if (e.key === "Escape") { | ||||||
|  |         modal_open = false; | ||||||
|  |       } | ||||||
|  |       if (e.keyCode === 13) { | ||||||
|  |         if (createbtnenabled === true) { | ||||||
|  |           createbtnenabled = false; | ||||||
|  |           submit(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   })(); | ||||||
|  |   function submit() { | ||||||
|  |     if (processed_last_submit === true) { | ||||||
|  |       processed_last_submit = false; | ||||||
|  |       const toast = Toastify({ | ||||||
|  |         text: $_('adding-scan'), | ||||||
|  |         duration: -1, | ||||||
|  |       }).showToast(); | ||||||
|  |       let postdata = { | ||||||
|  |         runner, | ||||||
|  |         distance: distance_input, | ||||||
|  |       }; | ||||||
|  |       ScanService.scanControllerPost(postdata) | ||||||
|  |         .then((result) => { | ||||||
|  |           runner = 0; | ||||||
|  |           distance_input = 0; | ||||||
|  |           modal_open = false; | ||||||
|  |           // | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_('scan-added'), | ||||||
|  |             duration: 500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |           current_scans.push(result); | ||||||
|  |           current_scans = current_scans; | ||||||
|  |         }) | ||||||
|  |         .catch((err) => { | ||||||
|  |           // | ||||||
|  |         }) | ||||||
|  |         .finally(() => { | ||||||
|  |           processed_last_submit = true; | ||||||
|  |           // | ||||||
|  |           toast.hideToast(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if modal_open} | ||||||
|  |   <div | ||||||
|  |     class="fixed z-10 inset-0 overflow-y-auto" | ||||||
|  |     use:focusTrap | ||||||
|  |     use:clickOutside | ||||||
|  |     on:click_outside={() => { | ||||||
|  |       modal_open = false; | ||||||
|  |     }}> | ||||||
|  |     <div | ||||||
|  |       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||||
|  |       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||||
|  |         <div | ||||||
|  |           class="absolute inset-0 bg-gray-500 opacity-75" | ||||||
|  |           data-id="modal_backdrop" /> | ||||||
|  |       </div> | ||||||
|  |       <span | ||||||
|  |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|  |         aria-hidden="true">​</span> | ||||||
|  |       <div | ||||||
|  |         class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|  |         role="dialog" | ||||||
|  |         aria-modal="true" | ||||||
|  |         aria-labelledby="modal-headline"> | ||||||
|  |         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||||
|  |           <div class="sm:flex sm:items-start"> | ||||||
|  |             <div | ||||||
|  |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|  |               <svg | ||||||
|  |                 class="h-6 w-6 text-blue-600" | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   fill="currentColor" | ||||||
|  |                   d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|  |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |                 {$_('create-a-new-scan-fixed-only')} | ||||||
|  |               </h3> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <p class="text-sm text-gray-500"> | ||||||
|  |                   {$_('please-provide-the-nessecary-information-to-create-a-new-scan')} | ||||||
|  |                 </p> | ||||||
|  |               </div> | ||||||
|  |               <div class="grid grid-cols-6 gap-6"> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="donor" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('runner')}</label> | ||||||
|  |                   <Select | ||||||
|  |                     containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|  |                     itemFilter={(label, filterText, option) => filterRunners(label, filterText, option)} | ||||||
|  |                     items={runners} | ||||||
|  |                     showChevron={true} | ||||||
|  |                     placeholder={$_('search-for-runner-by-name-or-id')} | ||||||
|  |                     noOptionsMessage={$_('no-runners-found')} | ||||||
|  |                     on:select={(selectedValue) => (runner = selectedValue.detail.value.id)} | ||||||
|  |                     on:clear={() => (runner = null)} /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="donation_amount_eur" | ||||||
|  |                     class="block text-sm font-medium text-gray-700"> | ||||||
|  |                     {$_('distance')}</label> | ||||||
|  |                   <div class="mt-1 flex rounded-md shadow-sm"> | ||||||
|  |                     <input | ||||||
|  |                       autocomplete="off" | ||||||
|  |                       class:border-red-500={!is_distance_valid} | ||||||
|  |                       class:focus:border-red-500={!is_distance_valid} | ||||||
|  |                       class:focus:ring-red-500={!is_distance_valid} | ||||||
|  |                       bind:value={distance_input} | ||||||
|  |                       type="number" | ||||||
|  |                       step="1" | ||||||
|  |                       name="donation_amount_eur" | ||||||
|  |                       class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2" | ||||||
|  |                       placeholder="400" /> | ||||||
|  |                     <span | ||||||
|  |                       class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">m</span> | ||||||
|  |                   </div> | ||||||
|  |                   {#if !is_distance_valid} | ||||||
|  |                     <span | ||||||
|  |                       class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |                       {$_('the-scans-distance-must-be-greater-than-0m')} | ||||||
|  |                     </span> | ||||||
|  |                   {/if} | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||||
|  |           <button | ||||||
|  |             disabled={!createbtnenabled} | ||||||
|  |             class:opacity-50={!createbtnenabled} | ||||||
|  |             on:click={submit} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('create')} | ||||||
|  |           </button> | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |               modal_open = false; | ||||||
|  |             }} | ||||||
|  |             type="button" | ||||||
|  |             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('cancel')} | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
							
								
								
									
										272
									
								
								src/components/scans/ScanDetail.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,272 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import { | ||||||
|  |     RunnerService, | ||||||
|  |     ScanService, | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import PromiseError from "../base/PromiseError.svelte"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|  |   let data_loaded = false; | ||||||
|  |   export let params; | ||||||
|  |   $: delete_triggered = false; | ||||||
|  |   $: original_data = {}; | ||||||
|  |   $: editable = {}; | ||||||
|  |   $: current_runners = []; | ||||||
|  |   $: is_distance_valid = editable.distance > 0; | ||||||
|  |   $: is_everything_set = | ||||||
|  |     editable.runner != null && | ||||||
|  |     ((original_data.responseType === "TRACKSCAN" && editable.track != null) || | ||||||
|  |       original_data.responseType !== "TRACKSCAN"); | ||||||
|  |   $: runner = {}; | ||||||
|  |   $: changes_performed = !( | ||||||
|  |     JSON.stringify(original_data) === JSON.stringify(editable) | ||||||
|  |   ); | ||||||
|  |   $: save_enabled = changes_performed && is_everything_set && is_distance_valid; | ||||||
|  |  | ||||||
|  |   const promise = ScanService.scanControllerGetOne(params.scanid).then( | ||||||
|  |     (data) => { | ||||||
|  |       data_loaded = true; | ||||||
|  |       original_data = Object.assign(original_data, data); | ||||||
|  |       original_data.runner = original_data.runner.id; | ||||||
|  |       editable = Object.assign(editable, original_data); | ||||||
|  |       RunnerService.runnerControllerGetAll().then( | ||||||
|  |         (val) => { | ||||||
|  |           current_runners = val.map((r) => { | ||||||
|  |             return { label: getRunnerLabel(r), value: r }; | ||||||
|  |           }); | ||||||
|  |           runner = current_runners.find(r => r.value.id == editable.runner); | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   const getRunnerLabel = (option) => | ||||||
|  |     option.firstname + " " + (option.middlename || "") + " " + option.lastname; | ||||||
|  |   const filterRunners = (label, filterText, option) => | ||||||
|  |     label.toLowerCase().includes(filterText.toLowerCase()) || | ||||||
|  |     option.value.id.toString().startsWith(filterText.toLowerCase()); | ||||||
|  |  | ||||||
|  |   function submit() { | ||||||
|  |     if (data_loaded === true && save_enabled) { | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_('scan-is-being-updated'), | ||||||
|  |         duration: 2500, | ||||||
|  |       }).showToast(); | ||||||
|  |       let postdata = {}; | ||||||
|  |       if (original_data.responseType === "TRACKSCAN") { | ||||||
|  |         postdata = Object.assign(postdata, editable); | ||||||
|  |         postdata.track = postdata.track.id; | ||||||
|  |         ScanService.scanControllerPutTrackScan(original_data.id, postdata) | ||||||
|  |           .then((resp) => { | ||||||
|  |             Object.assign(original_data, editable); | ||||||
|  |             original_data = original_data; | ||||||
|  |             Toastify({ | ||||||
|  |               text: $_('updated-scan'), | ||||||
|  |               duration: 2500, | ||||||
|  |               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |             }).showToast(); | ||||||
|  |           }) | ||||||
|  |           .catch((err) => {}); | ||||||
|  |       } else { | ||||||
|  |         postdata = Object.assign(postdata, editable); | ||||||
|  |         ScanService.scanControllerPut(original_data.id, postdata) | ||||||
|  |           .then((resp) => { | ||||||
|  |             Object.assign(original_data, editable); | ||||||
|  |             original_data = original_data; | ||||||
|  |             Toastify({ | ||||||
|  |               text: $_('updated-scan'), | ||||||
|  |               duration: 2500, | ||||||
|  |               backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |             }).showToast(); | ||||||
|  |           }) | ||||||
|  |           .catch((err) => {}); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   function deleteScan() { | ||||||
|  |     ScanService.scanControllerRemove(original_data.id, false) | ||||||
|  |       .then((resp) => { | ||||||
|  |         Toastify({ | ||||||
|  |           text: $_('deleted-scan'), | ||||||
|  |           duration: 500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |         location.replace("./"); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => { | ||||||
|  |         modal_open = true; | ||||||
|  |         delete_scan = original_data; | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  |   function format_laptime(laptime){ | ||||||
|  |     if(laptime == 0 || laptime == null){return $_('first-scan-of-the-day')} | ||||||
|  |     if(laptime < 60){return `${laptime}s`} | ||||||
|  |     if(laptime < 3600){return `${Math.floor(laptime / 60)}min ${laptime - (Math.floor(laptime / 60)*60)}s`} | ||||||
|  |     return `${Math.floor(laptime / 3600)}h ${laptime - (Math.floor(laptime / 3600)*3600)}min ${laptime - (Math.floor(laptime / 3600)*3600) - (Math.floor(laptime / 60)*60)}` | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#await promise} | ||||||
|  |   Loading scan details | ||||||
|  | {:then} | ||||||
|  |   <section class="container p-5 select-none"> | ||||||
|  |     <div class="flex flex-row mb-4"> | ||||||
|  |       <div class="w-full"> | ||||||
|  |         <nav class="w-full flex"> | ||||||
|  |           <ol class="list-none flex flex-row items-center justify-start"> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <svg | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path | ||||||
|  |                   fill="currentColor" | ||||||
|  |                   d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center ml-2"> | ||||||
|  |               <a class="mr-2" href="./">Scans</a><svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 mr-2 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><line | ||||||
|  |                   x1="5" | ||||||
|  |                   y1="12" | ||||||
|  |                   x2="19" | ||||||
|  |                   y2="12" /> | ||||||
|  |                 <polyline points="12 5 19 12 12 19" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <span class="mr-2">{original_data.id}</span> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </nav> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="mb-8 text-3xl font-extrabold leading-tight"> | ||||||
|  |       {runner.value?.firstname} | ||||||
|  |       {runner.value?.middlename || ''} | ||||||
|  |       {runner.value?.lastname} | ||||||
|  |       #{original_data.id} | ||||||
|  |       <span data-id="donation_actions_${original_data.id}"> | ||||||
|  |         {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:DELETE')} | ||||||
|  |           {#if delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={deleteScan} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:">{$_('confirm-deletion')}</button> | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = !delete_triggered; | ||||||
|  |               }} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:">{$_('cancel')}</button> | ||||||
|  |           {/if} | ||||||
|  |           {#if !delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = true; | ||||||
|  |               }} | ||||||
|  |               type="button" | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:">{$_('delete-scan')}</button> | ||||||
|  |           {/if} | ||||||
|  |         {/if} | ||||||
|  |         {#if !delete_triggered} | ||||||
|  |           <button | ||||||
|  |             disabled={!save_enabled} | ||||||
|  |             class:opacity-50={!save_enabled} | ||||||
|  |             type="button" | ||||||
|  |             on:click={submit} | ||||||
|  |             class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:">{$_('save-changes')}</button> | ||||||
|  |         {/if} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |     <!--  --> | ||||||
|  |     <div class="w-full inline-flex"> | ||||||
|  |       <label for="valid" class="block font-medium text-gray-700">{$_('status')}: | ||||||
|  |       </label> | ||||||
|  |         | ||||||
|  |       <input | ||||||
|  |         id="valid" | ||||||
|  |         on:change={() => { | ||||||
|  |           editable.valid = !editable.valid; | ||||||
|  |         }} | ||||||
|  |         name="valid" | ||||||
|  |         type="checkbox" | ||||||
|  |         checked={editable.valid} | ||||||
|  |         class="focus:ring-indigo-500 align-bottom h-7 w-5font-medium text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |         | ||||||
|  |       <p class="font-medium"> | ||||||
|  |         {#if editable.valid}{$_('valid')}{:else}{$_('invalid')}{/if} | ||||||
|  |       </p> | ||||||
|  |     </div> | ||||||
|  |     {#if editable.responseType === 'TRACKSCAN'} | ||||||
|  |       <div class="w-full inline-flex"> | ||||||
|  |         <label for="valid" class="block font-semibold text-gray-700">{$_('track')}: | ||||||
|  |         </label> | ||||||
|  |         <a | ||||||
|  |           href="../tracks" | ||||||
|  |           class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{editable.track.name} | ||||||
|  |         </a> | ||||||
|  |       </div> | ||||||
|  |       <div class="w-full inline-flex pb-3"> | ||||||
|  |         <label for="valid" class="block font-semibold text-gray-700">{$_('laptime')}: {format_laptime(editable.laptime)} | ||||||
|  |         </label> | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |     <div class=" w-full"> | ||||||
|  |       <label | ||||||
|  |         for="runner" | ||||||
|  |         class="block font-medium text-gray-700">{$_('runner')}</label> | ||||||
|  |       <Select | ||||||
|  |         containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|  |         itemFilter={(label, filterText, option) => filterRunners(label, filterText, option)} | ||||||
|  |         items={current_runners} | ||||||
|  |         showChevron={true} | ||||||
|  |         isDisabled={editable.responseType === 'TRACKSCAN'} | ||||||
|  |         placeholder={$_('search-for-runner-by-name-or-id')} | ||||||
|  |         noOptionsMessage={$_('no-runners-found')} | ||||||
|  |         bind:selectedValue={runner} | ||||||
|  |         on:select={(selectedValue) => { | ||||||
|  |           editable.runner = selectedValue.detail.value.id; | ||||||
|  |         }} | ||||||
|  |         on:clear={() => (editable.runner = null)} /> | ||||||
|  |     </div> | ||||||
|  |     <div class=" w-full"> | ||||||
|  |       <label | ||||||
|  |         for="scan_distance" | ||||||
|  |         class="block text-sm font-medium text-gray-700"> | ||||||
|  |         {$_('distance')}</label> | ||||||
|  |       <div class="mt-1 flex rounded-md shadow-sm"> | ||||||
|  |         <input | ||||||
|  |           autocomplete="off" | ||||||
|  |           class:border-red-500={!is_distance_valid} | ||||||
|  |           class:focus:border-red-500={!is_distance_valid} | ||||||
|  |           class:focus:ring-red-500={!is_distance_valid} | ||||||
|  |           bind:value={editable.distance} | ||||||
|  |           disabled={editable.responseType === 'TRACKSCAN'} | ||||||
|  |           type="number" | ||||||
|  |           step="1" | ||||||
|  |           name="scan_distance" | ||||||
|  |           class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2" | ||||||
|  |           placeholder="400" /> | ||||||
|  |         <span | ||||||
|  |           class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">m</span> | ||||||
|  |       </div> | ||||||
|  |       {#if !is_distance_valid} | ||||||
|  |         <span | ||||||
|  |           class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> | ||||||
|  |           {$_('the-scans-distance-must-be-greater-than-0m')} | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |   </section> | ||||||
|  | {:catch error} | ||||||
|  |   <PromiseError {error} /> | ||||||
|  | {/await} | ||||||
							
								
								
									
										29
									
								
								src/components/scans/Scans.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import AddScanModal from "./AddScanModal.svelte"; | ||||||
|  |   import ScansOverview from "./ScansOverview.svelte"; | ||||||
|  |   $: current_scans = []; | ||||||
|  |   export let modal_open = false; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <section class="container p-5"> | ||||||
|  |   <span class="mb-1 text-3xl font-extrabold leading-tight"> | ||||||
|  |     {$_('scans')} | ||||||
|  |     {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:CREATE')} | ||||||
|  |       <button | ||||||
|  |         on:click={() => { | ||||||
|  |           modal_open = true; | ||||||
|  |         }} | ||||||
|  |         type="button" | ||||||
|  |         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |        {$_('add-scan')} | ||||||
|  |       </button> | ||||||
|  |     {/if} | ||||||
|  |   </span> | ||||||
|  |   <ScansOverview bind:current_scans /> | ||||||
|  | </section> | ||||||
|  |  | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:CREATE')} | ||||||
|  |   <AddScanModal bind:current_scans bind:modal_open /> | ||||||
|  | {/if} | ||||||
							
								
								
									
										12
									
								
								src/components/scans/ScansEmptyState.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import scans_empty from "./scans.svg"; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div class="text-center items-center justify-center"> | ||||||
|  |   <p class="mb-16 text-lg text-gray-500"> | ||||||
|  |     <img class="m-auto" style="height:15rem" src={scans_empty} alt="" /> | ||||||
|  |     <span class="font-bold">{$_('there-are-no-scans-yet')}</span><br /> | ||||||
|  |     <span>{$_('add-your-fist-scan')}</span> | ||||||
|  |   </p> | ||||||
|  | </div> | ||||||
							
								
								
									
										201
									
								
								src/components/scans/ScansOverview.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,201 @@ | |||||||
|  | <script> | ||||||
|  |   import { getLocaleFromNavigator, _ } from "svelte-i18n"; | ||||||
|  |   import { | ||||||
|  |     ScanService, | ||||||
|  |   } from "@odit/lfk-client-js"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import ScansEmptyState from "./ScansEmptyState.svelte"; | ||||||
|  |   $: searchvalue = ""; | ||||||
|  |   $: active_deletes = []; | ||||||
|  |   export let current_scans = []; | ||||||
|  |   const scans_promise = ScanService.scanControllerGetAll().then((val) => { | ||||||
|  |     current_scans = val; | ||||||
|  |   }); | ||||||
|  |   function should_display_based_on_id(id) { | ||||||
|  |     if (searchvalue.toString().slice(-1) === "*") { | ||||||
|  |       return id.toString().startsWith(searchvalue.replace("*", "")); | ||||||
|  |     } | ||||||
|  |     return id.toString() === searchvalue; | ||||||
|  |   } | ||||||
|  |   function format_laptime(laptime){ | ||||||
|  |     if(laptime == 0 || laptime == null){return $_('first-scan-of-the-day')} | ||||||
|  |     if(laptime < 60){return `${laptime}s`} | ||||||
|  |     if(laptime < 3600){return `${Math.floor(laptime / 60)}min ${laptime - (Math.floor(laptime / 60)*60)}s`} | ||||||
|  |     return `${Math.floor(laptime / 3600)}h ${laptime - (Math.floor(laptime / 3600)*3600)}min ${laptime - (Math.floor(laptime / 3600)*3600) - (Math.floor(laptime / 60)*60)}` | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')} | ||||||
|  |   {#await scans_promise} | ||||||
|  |     <div | ||||||
|  |       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||||
|  |       role="alert"> | ||||||
|  |       <p class="font-bold">{$_('scans-are-being-loaded')}</p> | ||||||
|  |       <p class="text-sm">{$_('this-might-take-a-moment')}</p> | ||||||
|  |     </div> | ||||||
|  |   {:then} | ||||||
|  |     {#if current_scans.length === 0} | ||||||
|  |       <ScansEmptyState /> | ||||||
|  |     {:else} | ||||||
|  |       <input | ||||||
|  |         type="search" | ||||||
|  |         bind:value={searchvalue} | ||||||
|  |         placeholder={$_('datatable.search')} | ||||||
|  |         aria-label={$_('datatable.search')} | ||||||
|  |         class="gridjs-input gridjs-search-input mb-4" /> | ||||||
|  |       <div | ||||||
|  |         class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> | ||||||
|  |         <table class="divide-y divide-gray-200 w-full"> | ||||||
|  |           <thead class="bg-gray-50"> | ||||||
|  |             <tr> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('runner')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('distance-track')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('laptime')} | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|  |                 {$_('status')} | ||||||
|  |               </th> | ||||||
|  |               <th scope="col" class="relative px-6 py-3"> | ||||||
|  |                 <span class="sr-only">{$_('action')}</span> | ||||||
|  |               </th> | ||||||
|  |             </tr> | ||||||
|  |           </thead> | ||||||
|  |           <tbody class="divide-y divide-gray-200"> | ||||||
|  |             {#each current_scans as scan} | ||||||
|  |               {#if scan.track?.name | ||||||
|  |                 .toLowerCase() | ||||||
|  |                 .includes( | ||||||
|  |                   searchvalue.toLowerCase() | ||||||
|  |                 ) || scan.runner?.firstname | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || scan.runner?.middlename | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || scan.runner?.lastname | ||||||
|  |                   .toLowerCase() | ||||||
|  |                   .includes( | ||||||
|  |                     searchvalue.toLowerCase() | ||||||
|  |                   ) || should_display_based_on_id(scan.id)} | ||||||
|  |                 <tr data-rowid="scan_{scan.id}"> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <div class="flex items-center"> | ||||||
|  |                       <a | ||||||
|  |                         href="../runners/{scan.runner.id}" | ||||||
|  |                         class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.runner.firstname} | ||||||
|  |                         {scan.runner.middlename || ''} | ||||||
|  |                         {scan.runner.lastname}</a> | ||||||
|  |                     </div> | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                       {#if scan.distance < 1000} | ||||||
|  |                         {scan.distance}m | ||||||
|  |                       {:else}{scan.distance / 1000}km{/if} | ||||||
|  |                       {#if scan.track} | ||||||
|  |                         <a | ||||||
|  |                           href="../tracks" | ||||||
|  |                           class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.track.name} | ||||||
|  |                         </a> | ||||||
|  |                       {/if} | ||||||
|  |                     </div> | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     {#if scan.responseType === "TRACKSCAN"} | ||||||
|  |                       <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                         {format_laptime(scan.lapTime)} | ||||||
|  |                       </div> | ||||||
|  |                     {:else} | ||||||
|  |                       <div class="text-sm font-medium text-gray-900"> | ||||||
|  |                         {$_('scan-with-fixed-distance')} | ||||||
|  |                       </div> | ||||||
|  |                     {/if} | ||||||
|  |                   </td> | ||||||
|  |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|  |                     <div class="flex items-center"> | ||||||
|  |                       {#if scan.valid} | ||||||
|  |                         <span | ||||||
|  |                           class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('valid')}</span> | ||||||
|  |                       {:else} | ||||||
|  |                         <span | ||||||
|  |                           class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('invalid')}</span> | ||||||
|  |                       {/if} | ||||||
|  |                     </div> | ||||||
|  |                   </td> | ||||||
|  |  | ||||||
|  |                   {#if active_deletes[scan.id] === true} | ||||||
|  |                     <td | ||||||
|  |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                       <button | ||||||
|  |                         on:click={() => { | ||||||
|  |                           active_deletes[scan.id] = false; | ||||||
|  |                         }} | ||||||
|  |                         tabindex="0" | ||||||
|  |                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> | ||||||
|  |                       <button | ||||||
|  |                         on:click={() => { | ||||||
|  |                           ScanService.scanControllerRemove(scan.id, false).then( | ||||||
|  |                             (resp) => { | ||||||
|  |                               current_scans = current_scans.filter( | ||||||
|  |                                 (obj) => obj.id !== scan.id | ||||||
|  |                               ); | ||||||
|  |                               Toastify({ | ||||||
|  |                                 text: 'Scan deleted', | ||||||
|  |                                 duration: 500, | ||||||
|  |                                 backgroundColor: | ||||||
|  |                                   'linear-gradient(to right, #00b09b, #96c93d)', | ||||||
|  |                               }).showToast(); | ||||||
|  |                             } | ||||||
|  |                           ); | ||||||
|  |                         }} | ||||||
|  |                         tabindex="0" | ||||||
|  |                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> | ||||||
|  |                     </td> | ||||||
|  |                   {:else} | ||||||
|  |                     <td | ||||||
|  |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|  |                       <a | ||||||
|  |                         href="./{scan.id}" | ||||||
|  |                         class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> | ||||||
|  |                       {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:DELETE')} | ||||||
|  |                         <button | ||||||
|  |                           on:click={() => { | ||||||
|  |                             active_deletes[scan.id] = true; | ||||||
|  |                           }} | ||||||
|  |                           tabindex="0" | ||||||
|  |                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> | ||||||
|  |                       {/if} | ||||||
|  |                     </td> | ||||||
|  |                   {/if} | ||||||
|  |                 </tr> | ||||||
|  |               {/if} | ||||||
|  |             {/each} | ||||||
|  |           </tbody> | ||||||
|  |         </table> | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  |   {:catch error} | ||||||
|  |     <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> | ||||||
|  |       <span class="inline-block align-middle mr-8"> | ||||||
|  |         <b class="capitalize">{$_('general_promise_error')}</b> | ||||||
|  |         {error} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |   {/await} | ||||||
|  | {/if} | ||||||
							
								
								
									
										1
									
								
								src/components/scans/scans.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 647.6 632.2"><path d="M411 142H237a15 15 0 00-15 15v388l-2 1-43 13a8 8 0 01-10-6L39 137a8 8 0 016-10l66-20 191-58 66-20a8 8 0 0110 5l33 106z" fill="#f2f2f2"/><path d="M449 140L410 12a17 17 0 00-21-11l-93 28-191 59-93 28a17 17 0 00-11 21l134 438a17 17 0 0016 12 17 17 0 005 0l64-20 2-1v-2l-2 1-65 20a15 15 0 01-18-10L3 137a15 15 0 0110-19l92-28 192-59 92-28a15 15 0 015-1 15 15 0 0114 11l39 127 1 2h2z" fill="#3f3d56"/><path d="M123 128a9 9 0 01-9-7l-13-42a9 9 0 016-11l176-54a9 9 0 0111 6l13 42a9 9 0 01-6 12l-176 53a9 9 0 01-2 1z" fill="#6c63ff"/><circle cx="190.2" cy="25" r="20" fill="#6c63ff"/><circle cx="190.2" cy="25" r="12.7" fill="#fff"/><path d="M603 582H265a9 9 0 01-9-8V169a9 9 0 019-9h338a9 9 0 018 9v405a9 9 0 01-8 8z" fill="#e6e6e6"/><path d="M447 140H237a17 17 0 00-17 17v408l2-1V157a15 15 0 0115-15h211zm184 0H237a17 17 0 00-17 17v458a17 17 0 0017 17h394a17 17 0 0017-17V157a17 17 0 00-17-17zm15 475a15 15 0 01-15 15H237a15 15 0 01-15-15V157a15 15 0 0115-15h394a15 15 0 0115 15z" fill="#3f3d56"/><path d="M526 184H342a9 9 0 01-9-9v-44a9 9 0 019-9h184a9 9 0 019 9v44a9 9 0 01-9 9z" fill="#6c63ff"/><circle cx="433.6" cy="105.2" r="20" fill="#6c63ff"/><circle cx="433.6" cy="105.2" r="12.2" fill="#fff"/></svg> | ||||||
| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										205
									
								
								src/components/scanstations/AddScanStationModal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,205 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { clickOutside } from "../base/outsideclick"; | ||||||
|  |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|  |   import { ScanStationService, TrackService } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|  |   export let modal_open; | ||||||
|  |   export let new_station; | ||||||
|  |   export let current_stations; | ||||||
|  |   export let copy_modal_open; | ||||||
|  |   let tracks = []; | ||||||
|  |   TrackService.trackControllerGetAll().then((val) => { | ||||||
|  |     tracks = val.map((t) => { | ||||||
|  |       return { label: t.name || `#${t.id}`, value: t }; | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   function focus(el) { | ||||||
|  |     el.focus(); | ||||||
|  |   } | ||||||
|  |   $: description = ""; | ||||||
|  |   $: track = null; | ||||||
|  |   $: enabled = true; | ||||||
|  |   $: createbtnenabled = track != null; | ||||||
|  |   $: processed_last_submit = true; | ||||||
|  |   (() => { | ||||||
|  |     document.onkeydown = (e) => { | ||||||
|  |       e = e || window.event; | ||||||
|  |       if (e.key === "Escape") { | ||||||
|  |         modal_open = false; | ||||||
|  |       } | ||||||
|  |       if (e.keyCode === 13) { | ||||||
|  |         if (createbtnenabled === true) { | ||||||
|  |           createbtnenabled = false; | ||||||
|  |           submit(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   })(); | ||||||
|  |   function submit() { | ||||||
|  |     if (processed_last_submit === true) { | ||||||
|  |       processed_last_submit = false; | ||||||
|  |       const toast = Toastify({ | ||||||
|  |         text: $_("scanstation-is-being-added"), | ||||||
|  |         duration: -1, | ||||||
|  |       }).showToast(); | ||||||
|  |       let postdata = { | ||||||
|  |         description, | ||||||
|  |         enabled, | ||||||
|  |         track, | ||||||
|  |       }; | ||||||
|  |       ScanStationService.scanStationControllerPost(postdata) | ||||||
|  |         .then((result) => { | ||||||
|  |           description = ""; | ||||||
|  |           track = tracks[0].id; | ||||||
|  |           enabled = true; | ||||||
|  |           modal_open = false; | ||||||
|  |           // | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_("scanstation-added"), | ||||||
|  |             duration: 500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |           current_stations.push(result); | ||||||
|  |           current_stations = current_stations; | ||||||
|  |           new_station = result; | ||||||
|  |           copy_modal_open = true; | ||||||
|  |         }) | ||||||
|  |         .catch((err) => { | ||||||
|  |           // | ||||||
|  |         }) | ||||||
|  |         .finally(() => { | ||||||
|  |           processed_last_submit = true; | ||||||
|  |           // | ||||||
|  |           toast.hideToast(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if modal_open} | ||||||
|  |   <div | ||||||
|  |     class="fixed z-10 inset-0 overflow-y-auto" | ||||||
|  |     use:focusTrap | ||||||
|  |     use:clickOutside | ||||||
|  |     on:click_outside={() => { | ||||||
|  |       modal_open = false; | ||||||
|  |     }}> | ||||||
|  |     <div | ||||||
|  |       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||||
|  |       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||||
|  |         <div | ||||||
|  |           class="absolute inset-0 bg-gray-500 opacity-75" | ||||||
|  |           data-id="modal_backdrop" /> | ||||||
|  |       </div> | ||||||
|  |       <span | ||||||
|  |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|  |         aria-hidden="true">​</span> | ||||||
|  |       <div | ||||||
|  |         class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|  |         role="dialog" | ||||||
|  |         aria-modal="true" | ||||||
|  |         aria-labelledby="modal-headline"> | ||||||
|  |         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||||
|  |           <div class="sm:flex sm:items-start"> | ||||||
|  |             <div | ||||||
|  |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|  |               <svg | ||||||
|  |                 class="h-6 w-6 text-blue-600" | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|  |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |                 {$_('create-a-new-scanstation')} | ||||||
|  |               </h3> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <p class="text-sm text-gray-500"> | ||||||
|  |                   {$_('please-provide-the-required-information-to-create-a-new-scanstation')} | ||||||
|  |                 </p> | ||||||
|  |               </div> | ||||||
|  |               <div class="grid grid-cols-6 gap-6"> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="track" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">Track</label> | ||||||
|  |                   <Select | ||||||
|  |                     containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|  |                     itemFilter={(label, filterText, option) => label | ||||||
|  |                         .toLowerCase() | ||||||
|  |                         .includes( | ||||||
|  |                           filterText.toLowerCase() | ||||||
|  |                         ) || option.value.id | ||||||
|  |                         .toString() | ||||||
|  |                         .startsWith(filterText.toLowerCase())} | ||||||
|  |                     items={tracks} | ||||||
|  |                     showChevron={true} | ||||||
|  |                     placeholder="Search for a track (by name or id)." | ||||||
|  |                     noOptionsMessage="No track found" | ||||||
|  |                     on:select={(selectedValue) => (track = selectedValue.detail.value.id)} | ||||||
|  |                     on:clear={() => (track = null)} /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="description" | ||||||
|  |                     class="block text-sm font-medium text-gray-700">{$_('description')}</label> | ||||||
|  |                   <input | ||||||
|  |                     use:focus | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('description')} | ||||||
|  |                     bind:value={description} | ||||||
|  |                     type="text" | ||||||
|  |                     name="description" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-span-6"> | ||||||
|  |                   <label | ||||||
|  |                     for="enabled" | ||||||
|  |                     class="font-medium text-gray-700">{$_('enabled_large')}</label> | ||||||
|  |                   <br /> | ||||||
|  |                   <p class="text-gray-500"> | ||||||
|  |                     <input | ||||||
|  |                       id="enabled" | ||||||
|  |                       on:change={() => { | ||||||
|  |                         enabled = !enabled; | ||||||
|  |                       }} | ||||||
|  |                       name="enabled" | ||||||
|  |                       type="checkbox" | ||||||
|  |                       checked={enabled} | ||||||
|  |                       class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |                     {$_('this-scanstation-is')} | ||||||
|  |                     {#if enabled}{$_('enabled')}{:else}{$_('disabled')}{/if} | ||||||
|  |                   </p> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||||
|  |           <button | ||||||
|  |             disabled={!createbtnenabled} | ||||||
|  |             class:opacity-50={!createbtnenabled} | ||||||
|  |             on:click={submit} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('create')} | ||||||
|  |           </button> | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |               modal_open = false; | ||||||
|  |             }} | ||||||
|  |             type="button" | ||||||
|  |             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('cancel')} | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
| @@ -0,0 +1,92 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { clickOutside } from "../base/outsideclick"; | ||||||
|  |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|  |   import { ScanStationService } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import { createEventDispatcher } from "svelte"; | ||||||
|  |   export let modal_open; | ||||||
|  |   export let delete_station; | ||||||
|  |   const dispatch = createEventDispatcher(); | ||||||
|  |   function cancelDelete() { | ||||||
|  |     modal_open = false; | ||||||
|  |     dispatch("cancelDelete", { id: delete_station.id }); | ||||||
|  |   } | ||||||
|  |   function deleteStation() { | ||||||
|  |     ScanStationService.donorControllerRemove( | ||||||
|  |       delete_station.id, | ||||||
|  |       true | ||||||
|  |     ) | ||||||
|  |       .then((resp) => { | ||||||
|  |         Toastify({ | ||||||
|  |           text: $_('station-deleted'), | ||||||
|  |           duration: 500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |         location.replace("./"); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => {}); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if modal_open} | ||||||
|  |   <div | ||||||
|  |     class="fixed z-10 inset-0 overflow-y-auto" | ||||||
|  |     use:focusTrap | ||||||
|  |     use:clickOutside | ||||||
|  |     on:click_outside={cancelDelete}> | ||||||
|  |     <div | ||||||
|  |       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||||
|  |       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||||
|  |         <div | ||||||
|  |           class="absolute inset-0 bg-gray-500 opacity-75" | ||||||
|  |           data-id="modal_backdrop" /> | ||||||
|  |       </div> | ||||||
|  |       <span | ||||||
|  |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|  |         aria-hidden="true">​</span> | ||||||
|  |       <div | ||||||
|  |         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|  |         role="dialog" | ||||||
|  |         aria-modal="true" | ||||||
|  |         aria-labelledby="modal-headline"> | ||||||
|  |         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||||
|  |           <div class="sm:flex sm:items-start"> | ||||||
|  |             <div | ||||||
|  |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|  |               <svg class="h-6 w-6 text-blue-600" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"/></svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|  |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |                 {$_('attention')} | ||||||
|  |               </h3> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <p class="text-sm text-gray-500"> | ||||||
|  |                   {$_( | ||||||
|  |                     'do-you-want-to-delete-this-donor-with-all-related-donations' | ||||||
|  |                   )} | ||||||
|  |                   <br />  | ||||||
|  |                   {$_('all-associated-scans-will-get-deleted-as-well')} | ||||||
|  |                 </p> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||||
|  |           <button | ||||||
|  |             on:click={deleteStation} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('confirm-delete-station-with-all-scans')} | ||||||
|  |           </button> | ||||||
|  |           <button | ||||||
|  |             on:click={cancelDelete} | ||||||
|  |             type="button" | ||||||
|  |             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('cancel-keep-station')} | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
							
								
								
									
										125
									
								
								src/components/scanstations/CopyScanStationTokenModal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,125 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import { tick, createEventDispatcher } from "svelte"; | ||||||
|  |   export let copy_modal_open; | ||||||
|  |   export let new_station; | ||||||
|  |   const dispatch = createEventDispatcher(); | ||||||
|  |   let valueCopy = null; | ||||||
|  |   let areaDom; | ||||||
|  |   let copied = false; | ||||||
|  |   function close() { | ||||||
|  |     copy_modal_open = false; | ||||||
|  |   } | ||||||
|  |   async function copy() { | ||||||
|  |     valueCopy = new_station.key; | ||||||
|  |     await tick(); | ||||||
|  |     areaDom.focus(); | ||||||
|  |     areaDom.select(); | ||||||
|  |     try { | ||||||
|  |       const successful = document.execCommand("copy"); | ||||||
|  |       if (!successful) { | ||||||
|  |         throw new Error(); | ||||||
|  |       } | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_('copied-token-to-clipboard'), | ||||||
|  |         duration: 500, | ||||||
|  |         backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |       }).showToast(); | ||||||
|  |       copied = true; | ||||||
|  |     } catch (err) { | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_('error-whyile-copying-to-clipboard'), | ||||||
|  |         duration: 500, | ||||||
|  |         backgroundColor: | ||||||
|  |           "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", | ||||||
|  |       }).showToast(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // we can notifi by event or storage about copy status | ||||||
|  |     valueCopy = null; | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if copy_modal_open} | ||||||
|  |   {#if valueCopy != null} | ||||||
|  |     <textarea bind:this={areaDom}>{valueCopy}</textarea> | ||||||
|  |   {/if} | ||||||
|  |   <div class="fixed z-10 inset-0 overflow-y-auto" use:focusTrap> | ||||||
|  |     <div | ||||||
|  |       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||||
|  |       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||||
|  |         <div | ||||||
|  |           class="absolute inset-0 bg-gray-500 opacity-75" | ||||||
|  |           data-id="modal_backdrop" /> | ||||||
|  |       </div> | ||||||
|  |       <span | ||||||
|  |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|  |         aria-hidden="true">​</span> | ||||||
|  |       <div | ||||||
|  |         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|  |         role="dialog" | ||||||
|  |         aria-modal="true" | ||||||
|  |         aria-labelledby="modal-headline"> | ||||||
|  |         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||||
|  |           <div class="sm:flex sm:items-start"> | ||||||
|  |             <div | ||||||
|  |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|  |               <svg | ||||||
|  |                 class="h-6 w-6 text-blue-600" | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|  |               <h3 class="text-lg leading-6 font-medium text-gray-900">{$_('token')}</h3> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <p class="text-sm text-gray-500"> | ||||||
|  |                   {$_('the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again')} | ||||||
|  |                   <br /> | ||||||
|  |                   {$_('please-copy-the-token-and-store-it-somewhere-save')} | ||||||
|  |                 </p> | ||||||
|  |               </div> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <label | ||||||
|  |                   for="token" | ||||||
|  |                   class="block text-sm font-medium text-gray-700">Token</label> | ||||||
|  |                 <div on:click={copy} class="inline-flex"> | ||||||
|  |                   <p | ||||||
|  |                     name="token" | ||||||
|  |                     class:bg-green-200={copied} | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"> | ||||||
|  |                     {new_station.key} | ||||||
|  |                   </p> | ||||||
|  |                   <div | ||||||
|  |                     class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer"> | ||||||
|  |                     <svg | ||||||
|  |                       xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                       viewBox="0 0 24 24" | ||||||
|  |                       width="24" | ||||||
|  |                       height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                       <path | ||||||
|  |                         fill="currentColor" | ||||||
|  |                         d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |                 <p class="text-gray-500 text-xs">{$_('click-to-copy-token-to-clipboard')}</p> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||||
|  |           <button | ||||||
|  |             on:click={close} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('yes-i-copied-the-token')} | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
							
								
								
									
										206
									
								
								src/components/scanstations/ScanStationDetail.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,206 @@ | |||||||
|  | <script> | ||||||
|  |   import { t, _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import { ScanStationService, TrackService } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import PromiseError from "../base/PromiseError.svelte"; | ||||||
|  |   import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte"; | ||||||
|  |   import Select from "svelte-select"; | ||||||
|  |   let data_loaded = false; | ||||||
|  |   let modal_open; | ||||||
|  |   let delete_station; | ||||||
|  |   export let params; | ||||||
|  |   $: delete_triggered = false; | ||||||
|  |   $: original_data = {}; | ||||||
|  |   $: editable = {}; | ||||||
|  |   $: tracks = []; | ||||||
|  |   $: track = {}; | ||||||
|  |   $: changes_performed = !( | ||||||
|  |     JSON.stringify(original_data) === JSON.stringify(editable) | ||||||
|  |   ); | ||||||
|  |   $: save_enabled = changes_performed; | ||||||
|  |   const promise = ScanStationService.scanStationControllerGetOne( | ||||||
|  |     params.stationid | ||||||
|  |   ).then((data) => { | ||||||
|  |     data_loaded = true; | ||||||
|  |     data.track = data.track.id; | ||||||
|  |     original_data = Object.assign(original_data, data); | ||||||
|  |     editable = Object.assign(editable, original_data); | ||||||
|  |     TrackService.trackControllerGetAll().then((val) => { | ||||||
|  |       tracks = val.map((t) => { | ||||||
|  |         return { label: t.name || `#{t.id}`, value: t }; | ||||||
|  |       }); | ||||||
|  |       track = tracks.find((t) => t.value.id == editable.track); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   function submit() { | ||||||
|  |     if (data_loaded === true && save_enabled) { | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_("station-is-being-updated"), | ||||||
|  |         duration: 2500, | ||||||
|  |       }).showToast(); | ||||||
|  |       ScanStationService.scanStationControllerPut(original_data.id, editable) | ||||||
|  |         .then((resp) => { | ||||||
|  |           Object.assign(original_data, editable); | ||||||
|  |           original_data = original_data; | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_("updated-station"), | ||||||
|  |             duration: 2500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |         }) | ||||||
|  |         .catch((err) => {}); | ||||||
|  |     } else { | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   function deleteStation() { | ||||||
|  |     ScanStationService.scanStationControllerRemove(original_data.id, false) | ||||||
|  |       .then((resp) => { | ||||||
|  |         Toastify({ | ||||||
|  |           text: $_("station-deleted"), | ||||||
|  |           duration: 500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |         location.replace("./"); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => { | ||||||
|  |         modal_open = true; | ||||||
|  |         delete_station = original_data; | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <ConfirmScanStationDeletion bind:modal_open bind:delete_station /> | ||||||
|  | {#await promise} | ||||||
|  |   {$_('loading-station-details')} | ||||||
|  | {:then} | ||||||
|  |   <section class="container p-5 select-none"> | ||||||
|  |     <div class="flex flex-row mb-4"> | ||||||
|  |       <div class="w-full"> | ||||||
|  |         <nav class="w-full flex"> | ||||||
|  |           <ol class="list-none flex flex-row items-center justify-start"> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <svg | ||||||
|  |                 fill="currentColor" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 width="24" | ||||||
|  |                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  |                 <path | ||||||
|  |                   d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center ml-2"> | ||||||
|  |               <a class="mr-2" href="./">{$_('scanstation')}</a><svg | ||||||
|  |                 stroke="currentColor" | ||||||
|  |                 fill="none" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 viewBox="0 0 24 24" | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 class="h-3 w-3 mr-2 stroke-current" | ||||||
|  |                 height="1em" | ||||||
|  |                 width="1em" | ||||||
|  |                 xmlns="http://www.w3.org/2000/svg"><line | ||||||
|  |                   x1="5" | ||||||
|  |                   y1="12" | ||||||
|  |                   x2="19" | ||||||
|  |                   y2="12" /> | ||||||
|  |                 <polyline points="12 5 19 12 12 19" /></svg> | ||||||
|  |             </li> | ||||||
|  |             <li class="flex items-center"> | ||||||
|  |               <span class="mr-2">#{original_data.id}</span> | ||||||
|  |             </li> | ||||||
|  |           </ol> | ||||||
|  |         </nav> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="mb-8 text-3xl font-extrabold leading-tight"> | ||||||
|  |       #{original_data.id} | ||||||
|  |       <span data-id="stations_actions_${editable.id}"> | ||||||
|  |         {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:DELETE')} | ||||||
|  |           {#if delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={deleteStation} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button> | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = !delete_triggered; | ||||||
|  |               }} | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> | ||||||
|  |           {/if} | ||||||
|  |           {#if !delete_triggered} | ||||||
|  |             <button | ||||||
|  |               on:click={() => { | ||||||
|  |                 delete_triggered = true; | ||||||
|  |               }} | ||||||
|  |               type="button" | ||||||
|  |               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-station')}</button> | ||||||
|  |           {/if} | ||||||
|  |         {/if} | ||||||
|  |         {#if !delete_triggered} | ||||||
|  |           <button | ||||||
|  |             disabled={!save_enabled} | ||||||
|  |             class:opacity-50={!save_enabled} | ||||||
|  |             type="button" | ||||||
|  |             on:click={submit} | ||||||
|  |             class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> | ||||||
|  |         {/if} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |     <!--  --> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label | ||||||
|  |         for="track" | ||||||
|  |         class="block text-sm font-medium text-gray-700">Track</label> | ||||||
|  |       <Select | ||||||
|  |         containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" | ||||||
|  |         itemFilter={(label, filterText, option) => label | ||||||
|  |             .toLowerCase() | ||||||
|  |             .includes( | ||||||
|  |               filterText.toLowerCase() | ||||||
|  |             ) || option.value.id | ||||||
|  |             .toString() | ||||||
|  |             .startsWith(filterText.toLowerCase())} | ||||||
|  |         items={tracks} | ||||||
|  |         showChevron={true} | ||||||
|  |         placeholder="Search for a track (by name or id)." | ||||||
|  |         noOptionsMessage="No track found" | ||||||
|  |         bind:selectedValue={track} | ||||||
|  |         on:select={(selectedValue) => (editable.track = selectedValue.detail.value.id)} | ||||||
|  |         on:clear={() => (track = null)} /> | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label | ||||||
|  |         for="description" | ||||||
|  |         class="font-medium text-gray-700">{$_('description')}</label> | ||||||
|  |       <input | ||||||
|  |         autocomplete="off" | ||||||
|  |         placeholder={$_('description')} | ||||||
|  |         type="text" | ||||||
|  |         bind:value={editable.description} | ||||||
|  |         name="description" | ||||||
|  |         class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> | ||||||
|  |     </div> | ||||||
|  |     <div class="text-sm w-full"> | ||||||
|  |       <label | ||||||
|  |         for="enabled" | ||||||
|  |         class="ml-1 font-medium text-gray-700">{$_('enabled')}</label> | ||||||
|  |       <br /> | ||||||
|  |       <p class="text-gray-500"> | ||||||
|  |         <input | ||||||
|  |           id="enabled" | ||||||
|  |           on:change={() => { | ||||||
|  |             editable.enabled = !editable.enabled; | ||||||
|  |           }} | ||||||
|  |           name="enabled" | ||||||
|  |           type="checkbox" | ||||||
|  |           checked={editable.enabled} | ||||||
|  |           class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||||
|  |         {$_('this-scanstation-is')} | ||||||
|  |         {#if editable.enabled}{$_('enabled')}{:else}{$_('disabled')}{/if} | ||||||
|  |       </p> | ||||||
|  |     </div> | ||||||
|  |   </section> | ||||||
|  | {:catch error} | ||||||
|  |   <PromiseError {error} /> | ||||||
|  | {/await} | ||||||
							
								
								
									
										33
									
								
								src/components/scanstations/ScanStations.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import AddScanStationModal from "./AddScanStationModal.svelte"; | ||||||
|  | import CopyScanStationTokenModal from "./CopyScanStationTokenModal.svelte"; | ||||||
|  |   import ScanStationsOverview from "./ScanStationsOverview.svelte"; | ||||||
|  |   export let modal_open = false; | ||||||
|  |   export let copy_modal_open = false; | ||||||
|  |   export let new_station = {}; | ||||||
|  |   let current_stations = []; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <section class="container p-5"> | ||||||
|  |   <span class="mb-1 text-3xl font-extrabold leading-tight"> | ||||||
|  |     {$_('scanstations')} | ||||||
|  |     {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:CREATE')} | ||||||
|  |       <button | ||||||
|  |         on:click={() => { | ||||||
|  |           modal_open = true; | ||||||
|  |         }} | ||||||
|  |         type="button" | ||||||
|  |         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |         {$_('create-a-new-scanstation')} | ||||||
|  |       </button> | ||||||
|  |     {/if} | ||||||
|  |   </span> | ||||||
|  |   <ScanStationsOverview bind:current_stations bind:modal_open bind:new_station bind:copy_modal_open /> | ||||||
|  | </section> | ||||||
|  |  | ||||||
|  | {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:CREATE')} | ||||||
|  | <AddScanStationModal bind:modal_open bind:current_stations bind:new_station bind:copy_modal_open/> | ||||||
|  | <CopyScanStationTokenModal bind:copy_modal_open bind:new_station /> | ||||||
|  | {/if} | ||||||
							
								
								
									
										21
									
								
								src/components/scanstations/ScanStationsEmptyState.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  | import AddScanStationModal from "./AddScanStationModal.svelte"; | ||||||
|  | import CopyScanStationTokenModal from "./CopyScanStationTokenModal.svelte"; | ||||||
|  |   import scanstations_empty from "./scanstations_empty.svg"; | ||||||
|  |   let modal_open = false; | ||||||
|  |   let copy_modal_open = false; | ||||||
|  |   let new_station = {}; | ||||||
|  |   let current_stations = []; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div class="text-center items-center justify-center"> | ||||||
|  |   <p class="mb-16 text-lg text-gray-500"> | ||||||
|  |     <img class="w-full h-44" src={scanstations_empty} alt="" /> | ||||||
|  |     <span class="font-bold">{$_('you-dont-have-any-scanstations-yet')}.</span><br /> | ||||||
|  |     <span>{$_('add-the-first-scanstation')}</span> | ||||||
|  |   </p> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | <AddScanStationModal bind:modal_open bind:current_stations bind:new_station bind:copy_modal_open/> | ||||||
|  | <CopyScanStationTokenModal bind:copy_modal_open bind:new_station /> | ||||||
| @@ -1,43 +1,40 @@ | |||||||
| <script> | <script> | ||||||
|   import { t, _ } from "svelte-i18n"; |   import { _ } from "svelte-i18n"; | ||||||
|   import Toastify from "toastify-js"; |   import Toastify from "toastify-js"; | ||||||
|   import { RunnerTeamService } from "@odit/lfk-client-js"; |   import { ScanStationService } from "@odit/lfk-client-js"; | ||||||
|   const teams_promise = RunnerTeamService.runnerTeamControllerGetAll(); |   const promise = ScanStationService.scanStationControllerGetAll().then( | ||||||
|   import { users as usersstore } from "../store.js"; |     (result) => { | ||||||
|   import store from "../store"; |       current_stations = result; | ||||||
|   import TeamsEmptyState from "./TeamsEmptyState.svelte"; |     } | ||||||
|   import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte"; |   ); | ||||||
|  |   import store from "../../store"; | ||||||
|  |   import ScanStationsEmptyState from "./ScanStationsEmptyState.svelte"; | ||||||
|  |   import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte"; | ||||||
|   $: searchvalue = ""; |   $: searchvalue = ""; | ||||||
|   $: active_deletes = []; |   $: active_deletes = []; | ||||||
|   export let current_teams = []; |   let delete_station = {}; | ||||||
|   let modal_open = false; |   let modal_open = false; | ||||||
|   let delete_team = {}; |   export let current_stations = []; | ||||||
|   usersstore.subscribe((val) => { |  | ||||||
|     current_teams = val; |  | ||||||
|   }); |  | ||||||
|   teams_promise.then((data) => { |  | ||||||
|     usersstore.set(data); |  | ||||||
|   }); |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <ConfirmTeamDeletion | <ConfirmScanStationDeletion | ||||||
|   on:cancelDelete={(event) => { |   on:cancelDelete={(event) => { | ||||||
|     modal_open = false; |     modal_open = false; | ||||||
|     active_deletes[event.detail.id] = false; |     active_deletes[event.detail.id] = false; | ||||||
|   }} |   }} | ||||||
|   bind:modal_open |   bind:modal_open | ||||||
|   bind:delete_team /> |   bind:delete_station /> | ||||||
| {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')} | {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')} | ||||||
|   {#await teams_promise} |   {#await promise} | ||||||
|     <div |     <div | ||||||
|       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" |       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||||
|       role="alert"> |       role="alert"> | ||||||
|       <p class="font-bold">teams are being loaded...</p> |       <p class="font-bold">{$_('scanstations-are-being-loaded')}</p> | ||||||
|       <p class="text-sm">{$_('this-might-take-a-moment')}</p> |       <p class="text-sm">{$_('this-might-take-a-moment')}</p> | ||||||
|     </div> |     </div> | ||||||
|   {:then} |   {:then} | ||||||
|     {#if current_teams.length === 0} |     {#if current_stations.length === 0} | ||||||
|       <TeamsEmptyState /> |       <ScanStationsEmptyState /> | ||||||
|     {:else} |     {:else} | ||||||
|       <input |       <input | ||||||
|         type="search" |         type="search" | ||||||
| @@ -53,35 +50,38 @@ | |||||||
|               <th |               <th | ||||||
|                 scope="col" |                 scope="col" | ||||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|                 Name |                 {$_('track')} | ||||||
|               </th> |               </th> | ||||||
|               <th |               <th | ||||||
|                 scope="col" |                 scope="col" | ||||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|                 {$_('organization')} |                 {$_('description')} | ||||||
|               </th> |               </th> | ||||||
|               <th |               <th | ||||||
|                 scope="col" |                 scope="col" | ||||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||||
|                 Contact |                 {$_('status')} | ||||||
|               </th> |               </th> | ||||||
|               <th scope="col" class="relative px-6 py-3"> |               <th scope="col" class="relative px-6 py-3"> | ||||||
|                 <span class="sr-only">Action</span> |                 <span class="sr-only">{$_('action')}</span> | ||||||
|               </th> |               </th> | ||||||
|             </tr> |             </tr> | ||||||
|           </thead> |           </thead> | ||||||
|           <tbody class="divide-y divide-gray-200"> |           <tbody class="divide-y divide-gray-200"> | ||||||
|             {#each current_teams as t} |             {#each current_stations as s} | ||||||
|               {#if Object.values(t) |               {#if Object.values(s) | ||||||
|                 .toString() |                 .toString() | ||||||
|                 .toLowerCase() |                 .toLowerCase() | ||||||
|                 .includes(searchvalue)} |                 .includes(searchvalue)} | ||||||
|                 <tr data-rowid="team_{t.id}"> |                 <tr data-rowid="station_{s.id}"> | ||||||
|                   <td class="px-6 py-4 whitespace-nowrap"> |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|                     <div class="flex items-center"> |                     <div class="flex items-center"> | ||||||
|                       <div class="ml-4"> |                       <div class="ml-4"> | ||||||
|                         <div class="text-sm font-medium text-gray-900"> |                         <div class="text-sm font-medium text-gray-900"> | ||||||
|                           {t.name} |                           <a | ||||||
|  |                             href="../tracks" | ||||||
|  |                             class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"> | ||||||
|  |                             {s.track.name || s.track.distance + 'm'}</a> | ||||||
|                         </div> |                         </div> | ||||||
|                       </div> |                       </div> | ||||||
|                     </div> |                     </div> | ||||||
| @@ -90,43 +90,38 @@ | |||||||
|                     <div class="flex items-center"> |                     <div class="flex items-center"> | ||||||
|                       <div class="ml-4"> |                       <div class="ml-4"> | ||||||
|                         <div class="text-sm font-medium text-gray-900"> |                         <div class="text-sm font-medium text-gray-900"> | ||||||
|                           {#if t.parentGroup} |                           {s.description} | ||||||
|                             <a |  | ||||||
|                               href="../orgs/{t.parentGroup.id}" |  | ||||||
|                               class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.parentGroup.name}</a> |  | ||||||
|                           {:else}no organization specified{/if} |  | ||||||
|                         </div> |                         </div> | ||||||
|                       </div> |                       </div> | ||||||
|                     </div> |                     </div> | ||||||
|                   </td> |                   </td> | ||||||
|                   <td class="px-6 py-4 whitespace-nowrap"> |                   <td class="px-6 py-4 whitespace-nowrap"> | ||||||
|                     <div class="flex items-center"> |                     <div class="flex items-center"> | ||||||
|                       <div class="ml-4"> |                       {#if s.enabled} | ||||||
|                         <div class="text-sm font-medium text-gray-900"> |                       <span | ||||||
|                           {#if t.contact} |                         class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('active')}</span> | ||||||
|                             {JSON.stringify(t.contact)} |                     {:else} | ||||||
|                           {:else}no contact specified{/if} |                       <span | ||||||
|                         </div> |                         class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('inactive')}</span> | ||||||
|  |                     {/if} | ||||||
|                       </div> |                       </div> | ||||||
|                     </div> |  | ||||||
|                   </td> |                   </td> | ||||||
|                   {#if active_deletes[t.id] === true} |                   {#if active_deletes[s.id] === true} | ||||||
|                     <td |                     <td | ||||||
|                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|                       <button |                       <button | ||||||
|                         on:click={() => { |                         on:click={() => { | ||||||
|                           active_deletes[t.id] = false; |                           active_deletes[s.id] = false; | ||||||
|                         }} |                         }} | ||||||
|                         tabindex="0" |                         tabindex="0" | ||||||
|                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel |                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> | ||||||
|                         Delete</button> |  | ||||||
|                       <button |                       <button | ||||||
|                         on:click={() => { |                         on:click={() => { | ||||||
|                           RunnerTeamService.runnerTeamControllerRemove(t.id, false) |                           ScanStationService.scanStationControllerRemove(s.id, false) | ||||||
|                             .then((resp) => { |                             .then((resp) => { | ||||||
|                               current_teams = current_teams.filter((obj) => obj.id !== t.id); |                               current_stations = current_stations.filter((obj) => obj.id !== s.id); | ||||||
|                               Toastify({ |                               Toastify({ | ||||||
|                                 text: 'Organization deleted', |                                 text: $_('station-deleted'), | ||||||
|                                 duration: 500, |                                 duration: 500, | ||||||
|                                 backgroundColor: |                                 backgroundColor: | ||||||
|                                   'linear-gradient(to right, #00b09b, #96c93d)', |                                   'linear-gradient(to right, #00b09b, #96c93d)', | ||||||
| @@ -134,26 +129,25 @@ | |||||||
|                             }) |                             }) | ||||||
|                             .catch((err) => { |                             .catch((err) => { | ||||||
|                               modal_open = true; |                               modal_open = true; | ||||||
|                               delete_team = t; |                               delete_station = s; | ||||||
|                             }); |                             }); | ||||||
|                         }} |                         }} | ||||||
|                         tabindex="0" |                         tabindex="0" | ||||||
|                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">Confirm |                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> | ||||||
|                         Delete</button> |  | ||||||
|                     </td> |                     </td> | ||||||
|                   {:else} |                   {:else} | ||||||
|                     <td |                     <td | ||||||
|                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> |                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||||
|                       <a |                       <a | ||||||
|                         href="./{t.id}" |                         href="/scanstations/{s.id}" | ||||||
|                         class="text-indigo-600 hover:text-indigo-900">Edit</a> |                         class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> | ||||||
|                       {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')} |                       {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:DELETE')} | ||||||
|                         <button |                         <button | ||||||
|                           on:click={() => { |                           on:click={() => { | ||||||
|                             active_deletes[t.id] = true; |                             active_deletes[s.id] = true; | ||||||
|                           }} |                           }} | ||||||
|                           tabindex="0" |                           tabindex="0" | ||||||
|                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">Delete</button> |                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> | ||||||
|                       {/if} |                       {/if} | ||||||
|                     </td> |                     </td> | ||||||
|                   {/if} |                   {/if} | ||||||
							
								
								
									
										1
									
								
								src/components/scanstations/scanstations_empty.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.0 KiB | 
							
								
								
									
										90
									
								
								src/components/settings/ConfirmProfileDeletion.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,90 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import { clickOutside } from "../base/outsideclick"; | ||||||
|  |   import { focusTrap } from "svelte-focus-trap"; | ||||||
|  |   import { MeService } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import { createEventDispatcher } from "svelte"; | ||||||
|  |   export let modal_open; | ||||||
|  |   export let delete_triggered; | ||||||
|  |   const dispatch = createEventDispatcher(); | ||||||
|  |   function cancelDelete() { | ||||||
|  |     modal_open = false; | ||||||
|  |     delete_triggered = false; | ||||||
|  |     dispatch("cancelDelete"); | ||||||
|  |   } | ||||||
|  |   function deleteMe() { | ||||||
|  |     MeService.meControllerRemove(true) | ||||||
|  |       .then((resp) => { | ||||||
|  |         Toastify({ | ||||||
|  |           text: "Profile deleted!", | ||||||
|  |           duration: 500, | ||||||
|  |           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |         }).showToast(); | ||||||
|  |         location.replace("../"); | ||||||
|  |       }) | ||||||
|  |       .catch((err) => {}); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | {#if modal_open} | ||||||
|  |   <div | ||||||
|  |     class="fixed z-10 inset-0 overflow-y-auto" | ||||||
|  |     use:focusTrap | ||||||
|  |     use:clickOutside | ||||||
|  |     on:click_outside={cancelDelete}> | ||||||
|  |     <div | ||||||
|  |       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||||
|  |       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||||
|  |         <div | ||||||
|  |           class="absolute inset-0 bg-gray-500 opacity-75" | ||||||
|  |           data-id="modal_backdrop" /> | ||||||
|  |       </div> | ||||||
|  |       <span | ||||||
|  |         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||||
|  |         aria-hidden="true">​</span> | ||||||
|  |       <div | ||||||
|  |         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||||
|  |         role="dialog" | ||||||
|  |         aria-modal="true" | ||||||
|  |         aria-labelledby="modal-headline"> | ||||||
|  |         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||||
|  |           <div class="sm:flex sm:items-start"> | ||||||
|  |             <div | ||||||
|  |               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||||
|  |               <svg class="h-6 w-6 text-blue-600" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M9.33 11.5h2.17A4.5 4.5 0 0116 16H9v1h8v-1a5.58 5.58 0 00-.89-3H19a5 5 0 014.52 2.85A13.15 13.15 0 0113 21c-2.76 0-5.1-.59-7-1.63v-9.3a6.97 6.97 0 013.33 1.43zM5 19a1 1 0 01-1 1H2a1 1 0 01-1-1v-9a1 1 0 011-1h2a1 1 0 011 1v9zM18 5a3 3 0 110 6 3 3 0 010-6zm-7-3a3 3 0 110 6 3 3 0 010-6z"/></svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||||
|  |               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |                 {$_('attention')} | ||||||
|  |               </h3> | ||||||
|  |               <div class="mt-2 mb-6"> | ||||||
|  |                 <p class="text-sm text-gray-500"> | ||||||
|  |                   {$_('do-you-really-want-to-delete-your-profile')} | ||||||
|  |                   <br />  | ||||||
|  |                   {$_('you-are-going-to-loose-all-permissions-and-access-to-the-runner-system')} | ||||||
|  |                   <br> | ||||||
|  |                   {$_('after-deletion-we-cant-restore-your-old-profile')} | ||||||
|  |                 </p> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||||
|  |           <button | ||||||
|  |             on:click={deleteMe} | ||||||
|  |             type="button" | ||||||
|  |             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('confirm-delete-my-user-profile')} | ||||||
|  |           </button> | ||||||
|  |           <button | ||||||
|  |             on:click={cancelDelete} | ||||||
|  |             type="button" | ||||||
|  |             class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |             {$_('cancel-keep-my-profile')} | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
							
								
								
									
										319
									
								
								src/components/settings/Settings.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,319 @@ | |||||||
|  | <script> | ||||||
|  |   import { _ } from "svelte-i18n"; | ||||||
|  |   import isEmail from "validator/es/lib/isEmail"; | ||||||
|  |   import { MeService } from "@odit/lfk-client-js"; | ||||||
|  |   import Toastify from "toastify-js"; | ||||||
|  |   import ConfirmProfileDeletion from "./ConfirmProfileDeletion.svelte"; | ||||||
|  |   $: data_loaded = false; | ||||||
|  |   $: delete_triggered = false; | ||||||
|  |   $: original_data = {}; | ||||||
|  |   $: editable = {}; | ||||||
|  |   $: modal_open = false; | ||||||
|  |   $: password_change = ""; | ||||||
|  |   $: password_confirm = ""; | ||||||
|  |   $: changes_performed = !( | ||||||
|  |     JSON.stringify(editable) === JSON.stringify(original_data) | ||||||
|  |   ); | ||||||
|  |   $: save_enabled = changes_performed && isEmail(editable.email); | ||||||
|  |   $: update_password_enabled = | ||||||
|  |     password_change.length > 0 && password_change === password_confirm; | ||||||
|  |   const user_promise = MeService.meControllerGet().then((data) => { | ||||||
|  |     data_loaded = true; | ||||||
|  |     data.groups = data.groups.map((g) => g.id); | ||||||
|  |     data.permissions = [0]; | ||||||
|  |     original_data = Object.assign(original_data, data); | ||||||
|  |     editable = Object.assign(editable, original_data); | ||||||
|  |   }); | ||||||
|  |   function submit() { | ||||||
|  |     if (data_loaded === true && save_enabled) { | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_("updating-your-profile"), | ||||||
|  |         duration: 2500, | ||||||
|  |       }).showToast(); | ||||||
|  |       MeService.meControllerPut(editable) | ||||||
|  |         .then((resp) => { | ||||||
|  |           original_data = Object.assign(original_data, editable); | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_("profile-updated"), | ||||||
|  |             duration: 2500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |         }) | ||||||
|  |         .catch((err) => {}); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   function changePassword() { | ||||||
|  |     if (data_loaded === true && update_password_enabled) { | ||||||
|  |       Toastify({ | ||||||
|  |         text: $_('changing-your-password'), | ||||||
|  |         duration: 2500, | ||||||
|  |       }).showToast(); | ||||||
|  |       let postdata = Object.assign({}, original_data); | ||||||
|  |       postdata.password = password_confirm; | ||||||
|  |       MeService.meControllerPut(postdata) | ||||||
|  |         .then((resp) => { | ||||||
|  |           password_confirm = ""; | ||||||
|  |           password_change = ""; | ||||||
|  |           postdata = {}; | ||||||
|  |           Toastify({ | ||||||
|  |             text: $_('password-changed'), | ||||||
|  |             duration: 2500, | ||||||
|  |             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||||
|  |           }).showToast(); | ||||||
|  |           setTimeout(() => { | ||||||
|  |             location.replace("./"); | ||||||
|  |           }, 500); | ||||||
|  |         }) | ||||||
|  |         .catch((err) => {}); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <ConfirmProfileDeletion bind:modal_open bind:delete_triggered /> | ||||||
|  | <div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12"> | ||||||
|  |   <div class="text-center mb-8"> | ||||||
|  |     <h1 | ||||||
|  |       class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"> | ||||||
|  |       🔨<br />{$_('settings')} | ||||||
|  |     </h1> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
|  | <div class="pt-0 pb-16 bg-gray-50 overflow-hidden lg:pt-12 lg:py-24"> | ||||||
|  |   <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> | ||||||
|  |     <div> | ||||||
|  |       <div class="md:grid md:grid-cols-3 md:gap-6"> | ||||||
|  |         <div class="md:col-span-1"> | ||||||
|  |           <div class="px-4 sm:px-0"> | ||||||
|  |             <h3 class="text-lg font-medium leading-6 text-gray-900"> | ||||||
|  |               {$_('profile')} | ||||||
|  |             </h3> | ||||||
|  |             <p class="mt-1 text-sm text-gray-600"> | ||||||
|  |               {$_('everything-concerning-your-profile')} | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         {#await user_promise} | ||||||
|  |           {$_('loading-profile-data')} | ||||||
|  |         {:then} | ||||||
|  |           <div class="mt-5 md:mt-0 md:col-span-2"> | ||||||
|  |             <div class="shadow sm:rounded-md sm:overflow-hidden"> | ||||||
|  |               <div class="px-4 py-5 bg-white space-y-6 sm:p-6"> | ||||||
|  |                 <div> | ||||||
|  |                   <!-- svelte-ignore a11y-label-has-associated-control --> | ||||||
|  |                   <label class="block text-sm font-medium text-gray-700"> | ||||||
|  |                     {$_('profile-picture')} | ||||||
|  |                   </label> | ||||||
|  |                   <div class="mt-2 flex items-center"> | ||||||
|  |                     <span | ||||||
|  |                       class="inline-block h-20 w-20 rounded-full overflow-hidden bg-gray-100"> | ||||||
|  |                       <img | ||||||
|  |                         alt={$_('profile-picture')} | ||||||
|  |                         class="h-20 w-20 rounded-full overflow-hidden bg-gray-100" | ||||||
|  |                         src={editable.profilePic || 'https://lauf-fuer-kaya.de/lfk-logo.png'} /> | ||||||
|  |                     </span> | ||||||
|  |                     <!-- <button | ||||||
|  |                     type="button" | ||||||
|  |                     class="ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> | ||||||
|  |                     Change | ||||||
|  |                   </button> --> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="text-sm w-full"> | ||||||
|  |                   <label | ||||||
|  |                     for="username" | ||||||
|  |                     class="font-medium text-gray-700">{$_('username')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('username')} | ||||||
|  |                     type="text" | ||||||
|  |                     bind:value={editable.username} | ||||||
|  |                     name="username" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="text-sm w-full"> | ||||||
|  |                   <label | ||||||
|  |                     for="email" | ||||||
|  |                     class="font-medium text-gray-700">{$_('e-mail-adress')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('e-mail-adress')} | ||||||
|  |                     type="email" | ||||||
|  |                     bind:value={editable.email} | ||||||
|  |                     name="email" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" /> | ||||||
|  |                 </div> | ||||||
|  |                 {#if !isEmail(editable.email)} | ||||||
|  |                   <span | ||||||
|  |                     class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">{$_('valid-email-is-required')}</span> | ||||||
|  |                 {/if} | ||||||
|  |                 <div class="text-sm w-full"> | ||||||
|  |                   <label | ||||||
|  |                     for="firstname" | ||||||
|  |                     class="font-medium text-gray-700">{$_('first-name')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('first-name')} | ||||||
|  |                     type="text" | ||||||
|  |                     bind:value={editable.firstname} | ||||||
|  |                     name="firstname" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="text-sm w-full"> | ||||||
|  |                   <label | ||||||
|  |                     for="middlename" | ||||||
|  |                     class="font-medium text-gray-700">{$_('middle-name')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('middle-name')} | ||||||
|  |                     type="text" | ||||||
|  |                     bind:value={editable.middlename} | ||||||
|  |                     name="middlename" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" /> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="text-sm w-full"> | ||||||
|  |                   <label | ||||||
|  |                     for="lastname" | ||||||
|  |                     class="font-medium text-gray-700">{$_('last-name')}</label> | ||||||
|  |                   <input | ||||||
|  |                     autocomplete="off" | ||||||
|  |                     placeholder={$_('last-name')} | ||||||
|  |                     type="text" | ||||||
|  |                     bind:value={editable.lastname} | ||||||
|  |                     name="lastname" | ||||||
|  |                     class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" /> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |               <div class="px-4 py-3 bg-gray-50 text-right sm:px-6"> | ||||||
|  |                 <button | ||||||
|  |                   type="submit" | ||||||
|  |                   disabled={!save_enabled} | ||||||
|  |                   class:opacity-50={!save_enabled} | ||||||
|  |                   on:click={submit} | ||||||
|  |                   class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |                   {$_('save-changes')} | ||||||
|  |                 </button> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         {/await} | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> | ||||||
|  |     <div> | ||||||
|  |       <div class="md:grid md:grid-cols-3 md:gap-6"> | ||||||
|  |         <div class="md:col-span-1"> | ||||||
|  |           <div class="px-4 sm:px-0"> | ||||||
|  |             <h3 class="text-lg font-medium leading-6 text-gray-900"> | ||||||
|  |               {$_('password')} | ||||||
|  |             </h3> | ||||||
|  |             <p class="mt-1 text-sm text-gray-600"> | ||||||
|  |               {$_('change-your-password-here')} | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         {#await user_promise} | ||||||
|  |           {$_('loading-profile-data')} | ||||||
|  |         {:then} | ||||||
|  |           <div class="mt-5 md:mt-0 md:col-span-2"> | ||||||
|  |             <div class="shadow sm:rounded-md sm:overflow-hidden"> | ||||||
|  |               <div class="px-4 py-3 bg-gray-50 text-left sm:px-6"> | ||||||
|  |                 <label | ||||||
|  |                   for="new_password" | ||||||
|  |                   class="font-medium text-gray-700">{$_('new-password')}</label> | ||||||
|  |                 <div class="-mt-px relative"> | ||||||
|  |                   <input | ||||||
|  |                     aria-label={$_('password')} | ||||||
|  |                     type="password" | ||||||
|  |                     required="" | ||||||
|  |                     bind:value={password_change} | ||||||
|  |                     class="border-gray-300 placeholder-gray-500 appearance-none rounded-md relative block w-full px-3 py-2 border focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm" | ||||||
|  |                     placeholder={$_('password')} /> | ||||||
|  |                 </div> | ||||||
|  |                 <label | ||||||
|  |                   for="new_password" | ||||||
|  |                   class="font-medium text-gray-700">{$_('confirm-the-new-password')}</label> | ||||||
|  |                 <div class="-mt-px relative"> | ||||||
|  |                   <input | ||||||
|  |                     aria-label={$_('password')} | ||||||
|  |                     type="password" | ||||||
|  |                     required="" | ||||||
|  |                     bind:value={password_confirm} | ||||||
|  |                     class="border-gray-300 placeholder-gray-500 appearance-none rounded-md relative block w-full px-3 py-2 border focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm" | ||||||
|  |                     placeholder={$_('password')} /> | ||||||
|  |                 </div> | ||||||
|  |                 {#if password_change != password_confirm && password_change.length > 0} | ||||||
|  |                   <span | ||||||
|  |                     class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">{$_('passwords-dont-match')}</span> | ||||||
|  |                 {/if} | ||||||
|  |               </div> | ||||||
|  |               <div class="px-4 py-3 bg-gray-50 text-right sm:px-6"> | ||||||
|  |                 <button | ||||||
|  |                   type="submit" | ||||||
|  |                   disabled={!update_password_enabled} | ||||||
|  |                   class:opacity-50={!update_password_enabled} | ||||||
|  |                   on:click={changePassword} | ||||||
|  |                   class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||||
|  |                   {$_('update-password')} | ||||||
|  |                 </button> | ||||||
|  |                 {#if update_password_enabled} | ||||||
|  |                 <p> | ||||||
|  |                   {$_('after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that')} | ||||||
|  |                 </p> | ||||||
|  |                 {/if} | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         {/await} | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> | ||||||
|  |     <div> | ||||||
|  |       <div class="md:grid md:grid-cols-3 md:gap-6"> | ||||||
|  |         <div class="md:col-span-1"> | ||||||
|  |           <div class="px-4 sm:px-0"> | ||||||
|  |             <h3 class="text-lg font-medium leading-6 text-gray-900"> | ||||||
|  |               {$_('danger-zone')} | ||||||
|  |             </h3> | ||||||
|  |             <p class="mt-1 text-sm text-gray-600"> | ||||||
|  |               {$_('stuff-that-could-harm-your-profile')} | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         {#await user_promise} | ||||||
|  |           {$_('loading-profile-data')} | ||||||
|  |         {:then} | ||||||
|  |           <div class="mt-5 md:mt-0 md:col-span-2"> | ||||||
|  |             <div class="shadow sm:rounded-md sm:overflow-hidden"> | ||||||
|  |               <div class="px-4 py-3 bg-gray-50 text-left sm:px-6"> | ||||||
|  |                 <span data-id="donor_actions_${editable.id}"> | ||||||
|  |                   {#if delete_triggered} | ||||||
|  |                     <button | ||||||
|  |                       on:click={() => { | ||||||
|  |                         modal_open = true; | ||||||
|  |                       }} | ||||||
|  |                       class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:">{$_('confirm-deletion')}</button> | ||||||
|  |                     <button | ||||||
|  |                       on:click={() => { | ||||||
|  |                         delete_triggered = !delete_triggered; | ||||||
|  |                       }} | ||||||
|  |                       class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:">{$_('cancel')}</button> | ||||||
|  |                   {/if} | ||||||
|  |                   {#if !delete_triggered} | ||||||
|  |                     <button | ||||||
|  |                       on:click={() => { | ||||||
|  |                         delete_triggered = true; | ||||||
|  |                       }} | ||||||
|  |                       type="button" | ||||||
|  |                       class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:">{$_('delete-profile')}</button> | ||||||
|  |                   {/if} | ||||||
|  |                 </span> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         {/await} | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||