Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Philipp Dormann 2021-03-20 17:04:43 +01:00
commit 3837c5673a
40 changed files with 3590 additions and 973 deletions

View File

@ -54,7 +54,7 @@ steps:
tags:
- dev
registry: registry.odit.services
mtu: 1000
trigger:
branch:
- dev

View File

@ -2,8 +2,370 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [0.8.0](https://git.odit.services/lfk/frontend/compare/0.7.0...0.8.0)
- 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)
> 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/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)
@ -19,6 +381,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)
- 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)
- 🚀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)
- 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)

View File

@ -1,23 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/lfk-logo.png">
<meta name="theme-color" content="#FFFFFF">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Lauf Für Kaya! - Admin" />
<title>Lauf für Kaya! - Admin</title>
__TAILWIND_INSERT__
</head>
<body>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.6.0-RELEASE_INFO</span>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script>
<script defer type="module" src="/_dist_/index.js"></script>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/lfk-logo.png">
<meta name="theme-color" content="#FFFFFF">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Lauf Für Kaya! - Admin" />
<title>Lauf für Kaya! - Admin</title>
__TAILWIND_INSERT__
</head>
<body>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.8.0-RELEASE_INFO</span>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script>
<script defer type="module" src="/_dist_/index.js"></script>
</body>
</html>

View File

@ -1,62 +1,61 @@
{
"name": "@odit/lfk-frontend",
"version": "0.6.0",
"scripts": {
"i18n-order": "node order.js",
"dev:all": "yarn prebuild && snowpack dev",
"dev": "cross-env NODE_ENV_ODIT=development_fast node template-copy.js && yarn build:sw && snowpack dev",
"build": "yarn prebuild && snowpack build",
"prebuild": "cross-env NODE_ENV_ODIT=production node template-copy.js && yarn build:sw",
"build:sw": "workbox generateSW workbox-config.js",
"release": "release-it",
"licenses:export": "license-exporter --json -o public"
},
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@odit/lfk-client-js": "0.4.5",
"csvtojson": "^2.0.10",
"gridjs": "3.3.0",
"localforage": "1.9.0",
"lodash.isequal": "^4.5.0",
"marked": "^2.0.0",
"svelte-focus-trap": "1.0.1",
"svelte-i18n": "3.3.2",
"svelte-select": "^3.16.1",
"tailwindcss": "2.0.3",
"tinro": "0.5.12",
"toastify-js": "1.9.3",
"validator": "13.5.2",
"xlsx": "^0.16.9"
},
"devDependencies": {
"@odit/license-exporter": "^0.0.10",
"@snowpack/plugin-svelte": "3.5.2",
"auto-changelog": "^2.2.1",
"autoprefixer": "10.2.4",
"cross-env": "^7.0.3",
"postcss": "8.2.6",
"postcss-load-config": "3.0.1",
"release-it": "^14.4.0",
"snowpack": "3.0.11",
"svelte": "3.32.3",
"svelte-preprocess": "4.6.8",
"workbox-cli": "6.1.0"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀RELEASE v${version}",
"push": false,
"tag": true,
"tagName": null,
"tagAnnotation": "v${version}"
},
"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"
}
}
}
{
"name": "@odit/lfk-frontend",
"version": "0.8.0",
"scripts": {
"i18n-order": "node order.js",
"dev:all": "yarn prebuild && snowpack dev",
"dev": "cross-env NODE_ENV_ODIT=development_fast node template-copy.js && yarn build:sw && snowpack dev",
"build": "yarn prebuild && snowpack build",
"prebuild": "cross-env NODE_ENV_ODIT=production node template-copy.js && yarn build:sw",
"build:sw": "workbox generateSW workbox-config.js",
"release": "release-it",
"licenses:export": "license-exporter --json -o public"
},
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@odit/lfk-client-js": "0.6.4",
"csvtojson": "^2.0.10",
"gridjs": "3.3.0",
"localforage": "1.9.0",
"marked": "^2.0.1",
"svelte-focus-trap": "1.0.1",
"svelte-i18n": "3.3.7",
"svelte-select": "^3.17.0",
"tailwindcss": "2.0.3",
"tinro": "0.6.1",
"toastify-js": "1.9.3",
"validator": "13.5.2",
"xlsx": "^0.16.9"
},
"devDependencies": {
"@odit/license-exporter": "^0.0.11",
"@snowpack/plugin-svelte": "3.5.2",
"auto-changelog": "^2.2.1",
"autoprefixer": "10.2.5",
"cross-env": "^7.0.3",
"postcss": "8.2.8",
"postcss-load-config": "3.0.1",
"release-it": "^14.4.1",
"snowpack": "3.0.13",
"svelte": "3.35.0",
"svelte-preprocess": "4.6.9",
"workbox-cli": "6.1.2"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀RELEASE v${version}",
"push": false,
"tag": true,
"tagName": null,
"tagAnnotation": "v${version}"
},
"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"
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -42,7 +42,7 @@
import MainDashContent from "./components/dashboard/MainDashContent.svelte";
import Users from "./components/users/Users.svelte";
import About from "./components/general/About.svelte";
import Settings from "./components/general/Settings.svelte";
import Settings from "./components/settings/Settings.svelte";
import Transition from "./components/base/Transition.svelte";
import Orgs from "./components/orgs/Orgs.svelte";
import Runners from "./components/runners/Runners.svelte";
@ -69,6 +69,11 @@
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();
registerSW();
</script>
@ -180,6 +185,22 @@
<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">
<About />
</Route>

View File

@ -172,21 +172,61 @@
<span>{$_('tracks')}</span>
</a>
{/if}
<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 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
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"

View File

@ -7,9 +7,15 @@
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();
}
@ -19,12 +25,14 @@
$: runners = [];
$: is_fixed = false;
DonorService.donorControllerGetAll().then((val) => {
donors = val;
donor = donors[0].id || 0;
donors = val.map((r) => {
return { label: getDonorLabel(r), value: r };
});
});
RunnerService.runnerControllerGetAll().then((val) => {
runners = val;
runner = runners[0].id || 0;
runners = val.map((r) => {
return { label: getDonorLabel(r), value: r };
});
});
$: amount_input = 0;
$: processed_last_submit = true;
@ -158,7 +166,7 @@
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</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"
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">
@ -178,8 +186,9 @@
</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">
<!-- TODO: -->
{#if is_fixed}{$_('create-a-new-fixed-donation')}{:else}{$_('create-a-new-distance-donation')}{/if}
{#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
@ -203,41 +212,39 @@
<label
for="donor"
class="block text-sm font-medium text-gray-700">{$_('donor')}</label>
<select
bind:value={donor}
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 donors as d}
<option value={d.id}>
{d.firstname}
{d.middlename || ''}
{d.lastname}
</option>
{/each}
</select>
<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
bind:value={runner}
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 runners as r}
<option value={r.id}>
{r.firstname}
{r.middlename || ''}
{r.lastname}
</option>
{/each}
</select>
<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>
{#if !is_fixed}
{$_('amount-per-kilometer')}
{:else}{$_('donation-amount')}{/if}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"

View File

@ -8,44 +8,60 @@
} 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 = {};
$: original_comparison_string = "";
$: 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 =
!(original_comparison_string === JSON.stringify(editable)) ||
!(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;
const donor_promise = DonorService.donorControllerGetAll().then((val) => {
current_donors = val;
});
const runner_promise = RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val;
});
$: 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);
editable.donor = data.donor.id;
if (data.responseType == "DISTANCEDONATION") {
editable.runner = data.runner.id;
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;
}
original_comparison_string = JSON.stringify(editable);
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) {
@ -53,14 +69,18 @@
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,
editable
postdata
)
.then((resp) => {
Object.assign(original_data, resp);
Object.assign(original_data, editable);
original_data = original_data;
Toastify({
text: "updated donation",
@ -71,7 +91,9 @@
.catch((err) => {});
} else {
editable.amount = Math.floor(amount_input * 100);
DonationService.donationControllerPutFixed(original_data.id, editable)
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;
@ -103,7 +125,7 @@
}
</script>
{#await donor_promise && runner_promise && promise}
{#await promise}
{$_('loading-donation-details')}
{:then}
<section class="container p-5 select-none">
@ -192,7 +214,8 @@
</div>
<!-- -->
<div>
<span class="font-medium text-gray-700">{$_('total-donation-amount')}:</span>
<span
class="font-medium text-gray-700">{$_('total-donation-amount')}:</span>
<span>{(editable.amount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}€</span>
@ -201,34 +224,32 @@
<label
for="donor"
class="block font-medium text-gray-700">{$_('donor')}</label>
<select
bind:value={editable.donor}
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">
{#each current_donors as d}
<option value={d.id}>
{d.firstname}
{d.middlename || ''}
{d.lastname}
</option>
{/each}
</select>
<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
bind:value={editable.runner}
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">
{#each current_runners as r}
<option value={r.id}>
{r.firstname}
{r.middlename || ''}
{r.lastname}
</option>
{/each}
</select>
<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">

View File

@ -1,35 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import FormLayout from "../base/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>

View File

@ -1,221 +1,220 @@
<script>
import { _ } from "svelte-i18n";
import lodashIsEqual from "lodash.isequal";
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 = !lodashIsEqual(original_data, 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}
<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}

View File

@ -67,7 +67,6 @@ UserGroupService,
delete p.responseType;
grantedPermissions = grantedPermissions.concat([p]);
});
console.log(grantedPermissions)
grantedPermissions_initial = grantedPermissions;
});
</script>

View File

@ -9,6 +9,7 @@
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) ||
@ -19,22 +20,19 @@
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;
if (value.contact) {
if (value.contact !== "null") {
value.contact = value.contact.id;
}
}
value.address_checked = value.address.address1 !== null;
if (value.address_checked === false) {
value.address = {
@ -49,16 +47,23 @@
editable = editable;
original_object = Object.assign(editable, value);
original = JSON.stringify(value);
});
GroupContactService.groupContactControllerGetAll().then((val) => {
contacts = val;
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"
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
@ -91,13 +96,14 @@
if (postdata.address_checked === false) {
postdata.address = null;
}
postdata.contact = postdata.contact === "null" ? null : postdata.contact;
postdata.contact = postdata.contact?.id;
RunnerOrganizationService.runnerOrganizationControllerPut(
original_object.id,
postdata
)
.then((resp) => {
original = JSON.stringify(editable);
original_object = Object.assign({}, editable);
original = JSON.stringify(original_object);
Toastify({
text: $_("updated-organization"),
duration: 2500,
@ -188,7 +194,17 @@
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>
<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}
@ -349,19 +365,22 @@
<label
for="contact"
class="font-medium text-gray-700">{$_('contact')}</label>
<select
name="contact"
bind:value={editable.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">
<option value="null">no contact</option>
{#each contacts as c}
<option value={c.id}>
{c.firstname}
{c.middlename || ''}
{c.lastname}
</option>
{/each}
</select>
<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">

View File

@ -20,8 +20,8 @@
document.addEventListener("click", function (e) {
if (
e.target.parentNode.parentNode.id != "sponsoring:dropdown" &&
e.target.parentNode.parentNode.id != "sponsoring:dropdown:menu"
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}

View File

@ -10,6 +10,7 @@
import isEmail from "validator/es/lib/isEmail";
import isMobilePhone from "validator/es/lib/isMobilePhone";
import Toastify from "toastify-js";
import Select from "svelte-select";
export let modal_open;
export let current_runners;
$: selected_team = undefined;
@ -20,11 +21,15 @@
let email_input;
let teams = [];
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
teams = val.map((r) => {
return { label: `${r.parentGroup.name} > ${r.name}`, value: r };
});
});
let orgs = [];
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
orgs = val.map((r) => {
return { label: r.name, value: r };
});
});
function focus(el) {
el.focus();
@ -136,7 +141,7 @@
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</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"
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">
@ -206,7 +211,7 @@
class="block text-sm font-medium text-gray-700">{$_('last-name')}</label>
<input
autocomplete="off"
placeholder="{$_('last-name')}"
placeholder={$_('last-name')}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
@ -226,21 +231,21 @@
<label
for="team"
class="block text-sm font-medium text-gray-700">{$_('team')}</label>
<select
name="team"
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}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value={org.id}>{org.name}</option>
{/each}
</select>
<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={orgs.concat(teams)}
showChevron={true}
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')}
noOptionsMessage={$_('no-organization-or-team-found')}
on:select={(selectedValue) => (selected_team = selectedValue.detail.value.id)}
on:clear={() => (selected_team = null)} />
</div>
<div class="col-span-6">
<label
@ -248,7 +253,7 @@
class="block text-sm font-medium text-gray-700">{$_('phone')}</label>
<input
autocomplete="off"
placeholder="{$_('phone')}"
placeholder={$_('phone')}
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}

View File

@ -11,6 +11,7 @@
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import { createEventDispatcher } from "svelte";
import Select from "svelte-select";
export let opened_from;
export let passed_org;
export let passed_orgs;
@ -35,22 +36,18 @@
}
};
})();
let orgs = [];
let groups = [];
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
if(opened_from === 'OrgOverview'){
selected_org = orgs[0].id
}
});
let teams = [];
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
if (opened_from === "RunnerOverview" && teams.length>0) {
selected_org_or_team = "TEAM_" + teams[0].id;
}
if(teams.length==0 && orgs.length>0){
selected_org_or_team = "ORG_" + orgs[0].id
}
const orgs = val.map((r) => {
return { label: r.name, value: `ORG_${r.id}` };
});
groups = groups.concat(orgs);
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;
$: selected_org_or_team = "";
@ -264,21 +261,23 @@
{/if}
{#if opened_from === 'RunnerOverview'}
<p>{$_('group')}</p>
<select
name="team"
bind:value={selected_org_or_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_{team.id}">
{team.parentGroup.name}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value="ORG_{org.id}">{org.name}</option>
{/each}
</select>
<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.id.value
.toString()
.startsWith(filterText.toLowerCase())}
items={groups}
showChevron={true}
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')}
noOptionsMessage={$_('no-organization-or-team-found')}
on:select={(selectedValue) => {
selected_org_or_team = selectedValue.detail.value;
}}
on:clear={() => (selected_org_or_team = null)} />
{/if}
{#if opened_from === 'OrgDetail'}
<p>

View File

@ -1,6 +1,5 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import lodashIsEqual from "lodash.isequal";
import store from "../../store";
import {
RunnerService,
@ -10,6 +9,7 @@
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail";
import Select from "svelte-select";
let data_loaded = false;
export let params;
const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid);
@ -18,48 +18,61 @@
$: original_data_pdf = {};
$: original_data = {};
$: editable = {};
$: changes_performed = !lodashIsEqual(original_data, editable);
$: group = {}
$: 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;
changes_performed &&
isFirstnameValid &&
isLastnameValid &&
isEmailValid &&
editable.group != null;
runner_promise.then((data) => {
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.group = original_data.group.id;
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)
});
});
});
document.addEventListener("click", function (e) {
if (
e.target.parentNode.parentNode.id != "sponsoring:dropdown" &&
e.target.parentNode.parentNode.id != "sponsoring:dropdown:menu"
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
let orgs = [];
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
let teams = [];
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
let groups = [];
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: $_("updating-runner"),
duration: 2500,
}).showToast();
RunnerService.runnerControllerPut(original_data.id, editable)
let postdata = {};
postdata = Object.assign(postdata, editable);
RunnerService.runnerControllerPut(original_data.id, postdata)
.then((resp) => {
Object.assign(original_data, editable);
original_data = editable;
Object.assign(original_data, editable);
original_data = original_data;
Toastify({
text: $_("runner-updated"),
duration: 2500,
@ -206,7 +219,17 @@
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>
<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}
@ -349,21 +372,20 @@
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('group')}</span>
<select
bind:value={editable.group}
name="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}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value={org.id}>{org.name}</option>
{/each}
</select>
<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.id.value.toString().startsWith(filterText.toLowerCase())}
items={groups}
showChevron={true}
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')}
noOptionsMessage={$_('no-organization-or-team-found')}
bind:selectedValue={group}
on:select={(selectedValue) => {editable.group = selectedValue.detail.value.id}}
on:clear={() => (editable.group = null)} />
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('distance')}</span>

View File

@ -33,8 +33,8 @@
.concat(mappedteams);
document.addEventListener("click", function (e) {
if (
e.target.parentNode.parentNode.id != "sponsoring:dropdown" &&
e.target.parentNode.parentNode.id != "sponsoring:dropdown:menu"
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}

View 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">&#8203;</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}

View 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>
&nbsp;
<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" />
&nbsp;
<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}

View 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}

View 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="w-full" 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>

View 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}

View 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

View 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">&#8203;</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}

View File

@ -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">&#8203;</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}

View 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">&#8203;</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}

View 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}

View 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}

View 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 />

View File

@ -0,0 +1,169 @@
<script>
import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import { ScanStationService } from "@odit/lfk-client-js";
const promise = ScanStationService.scanStationControllerGetAll().then(
(result) => {
current_stations = result;
}
);
import store from "../../store";
import ScanStationsEmptyState from "./ScanStationsEmptyState.svelte";
import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte";
$: searchvalue = "";
$: active_deletes = [];
let delete_station = {};
let modal_open = false;
export let current_stations = [];
</script>
<ConfirmScanStationDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open
bind:delete_station />
{#if store.state.jwtinfo.userdetails.permissions.includes('STATION: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">{$_('scanstations-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_stations.length === 0}
<ScanStationsEmptyState />
{: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">
{$_('track')}
</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="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_stations as s}
{#if Object.values(s)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr data-rowid="station_{s.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">
<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>
</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">
{s.description}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if s.enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('active')}</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('inactive')}</span>
{/if}
</div>
</td>
{#if active_deletes[s.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[s.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
ScanStationService.scanStationControllerRemove(s.id, false)
.then((resp) => {
current_stations = current_stations.filter((obj) => obj.id !== s.id);
Toastify({
text: $_('station-deleted'),
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_station = s;
});
}}
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="/scanstations/{s.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('STATION:DELETE')}
<button
on:click={() => {
active_deletes[s.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}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.0 KiB

View 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">&#8203;</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}

View 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>

View File

@ -6,6 +6,7 @@
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import Select from "svelte-select";
import Toastify from "toastify-js";
export let modal_open;
export let current_teams;
@ -34,7 +35,9 @@
$: parentGroup = undefined;
$: orgs = [];
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
orgs = val.map((r) => {
return { label: r.name, value: r };
});
});
function submit() {
if (processed_last_submit === true) {
@ -90,7 +93,7 @@
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</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"
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">
@ -145,13 +148,27 @@
<label
for="firstname"
class="block text-sm font-medium text-gray-700">{$_('organization')}</label>
<select
bind:value={parentGroup}
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 orgs as t}
<option value={t.id}>{t.name}</option>
{/each}
</select>
<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={orgs}
showChevron={true}
placeholder={$_('search-for-an-organization-by-name-or-id')}
noOptionsMessage={$_('no-organizations-found')}
on:select={(selectedValue) => (parentGroup = selectedValue.detail.value.id)}
on:clear={() => (parentGroup = null)} />
{#if !parentGroup}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('you-have-to-provide-an-organization')}
</span>
{/if}
</div>
</div>
</div>

View File

@ -7,9 +7,11 @@
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import Toastify from "toastify-js";
import store from "../../store";
import Select from "svelte-select";
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
import PromiseError from "../base/PromiseError.svelte";
import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte";
import Teams from "./Teams.svelte";
let [teamdata, original, delete_team, orgs, contacts, modal_open] = [
{},
{},
@ -21,33 +23,43 @@
export let params;
export let import_modal_open = false;
$: delete_triggered = false;
$: save_enabled = !data_changed;
$: save_enabled = !data_changed && teamdata.parentGroup != null;
$: data_loaded = false;
$: data_changed = JSON.stringify(teamdata) === JSON.stringify(original);
$: sponsoring_contracts_download_open = false;
$: group = {};
$: contact = {};
//
const getContactLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const promise = RunnerTeamService.runnerTeamControllerGetOne(
params.teamid
).then((value) => {
data_loaded = true;
if (value.contact) {
if (value.contact !== "null") {
value.contact = value.contact.id;
}
}
teamdata = Object.assign(teamdata, value);
original = Object.assign(original, value);
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
GroupContactService.groupContactControllerGetAll().then((val) => {
contacts = val;
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val.map((r) => {
return { label: r.name, value: r };
});
group = orgs.find((g) => g.value.id == teamdata.parentGroup.id);
});
GroupContactService.groupContactControllerGetAll().then((val) => {
contacts = val.map((r) => {
return { label: getContactLabel(r), value: r };
});
if(teamdata.contact){
contact = contacts.find((g) => g.value.id == teamdata.contact.id);
}
else{
contact = null;
}
});
});
document.addEventListener("click", function (e) {
if (
e.target.parentNode.parentNode.id != "sponsoring:dropdown" &&
e.target.parentNode.parentNode.id != "sponsoring:dropdown:menu"
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
@ -73,15 +85,13 @@
text: "updating team",
duration: 2500,
}).showToast();
teamdata.parentGroup = teamdata.parentGroup.id;
let postdata = teamdata;
postdata.contact = postdata.contact === "null" ? null : postdata.contact;
postdata.parentGroup = teamdata.parentGroup.id;
postdata.contact = teamdata.contact?.id;
RunnerTeamService.runnerTeamControllerPut(original.id, postdata)
.then((resp) => {
Object.assign(original, teamdata);
original = teamdata;
Object.assign(original, teamdata);
//
original = original;
Toastify({
text: "updated team",
duration: 2500,
@ -170,7 +180,17 @@
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>
<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}
@ -333,32 +353,41 @@
<label
for="contact"
class="font-medium text-gray-700">{$_('contact')}</label>
<select
name="contact"
bind:value={teamdata.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">
<option value="null">no contact</option>
{#each contacts as c}
<option value={c.id}>
{c.firstname}
{c.middlename || ''}
{c.lastname}
</option>
{/each}
</select>
<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)=> teamdata.contact = selectedValue.detail.value}
on:clear={() => (teamdata.contact = null)} />
</div>
<div class="text-sm w-full">
<label
for="org"
class="font-medium text-gray-700">{$_('organization')}</label>
<select
name="org"
bind:value={teamdata.parentGroup}
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 orgs as o}
<option value={o.id}>{o.name}</option>
{/each}
</select>
<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.id.value.toString().startsWith(filterText.toLowerCase())}
items={orgs}
showChevron={true}
placeholder={$_('search-for-an-organization-by-name-or-id')}
noOptionsMessage={$_('no-organizations-found')}
bind:selectedValue={group}
on:select={(selectedValue)=> teamdata.parentGroup = selectedValue.detail.value}
on:clear={() => (teamdata.parentGroup = null)} />
</div>
</section>
{:else}

View File

@ -6,6 +6,7 @@
import store, { users as usersstore } from "../../store.js";
import TeamsEmptyState from "./TeamsEmptyState.svelte";
import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte";
import { clickOutside } from "../base/outsideclick";
$: searchvalue = "";
$: active_deletes = [];
$: sponsoring_contracts_download_open = false;
@ -19,12 +20,12 @@
usersstore.set(data);
});
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;
}
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;
@ -111,54 +112,67 @@
class="gridjs-input gridjs-search-input mb-4" />
<div class="h-12">
{#if current_teams.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 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}
</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"
on:click_outside={() => {
sponsoring_contracts_download_open = false;
}}>
<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

View File

@ -1,327 +1,326 @@
<script>
import { _ } from "svelte-i18n";
import lodashIsEqual from "lodash.isequal";
import store from "../../store";
import isEmail from "validator/es/lib/isEmail";
import { UserService, UserGroupService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
export let params;
const user_promise = UserService.userControllerGetOne(params.userid);
let data_loaded = false;
let usergroups_array_original = [];
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;
$: original_data = {};
$: editable_userdata = {};
$: allgroups = [];
$: allgroups_ids = [];
$: usergroups_array = [];
$: search_permission = "";
user_promise.then((data) => {
let current_target = "";
let colorindex = -1;
// alphabetically sort permissions for color compatibility for target
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_userdata = data;
data.groups.forEach((g) => {
usergroups_array = usergroups_array.concat([g.id]);
});
usergroups_array_original = usergroups_array;
allgroups.forEach((g) => {
allgroups_ids.push(g.id);
});
});
UserGroupService.userGroupControllerGetAll().then((data) => {
allgroups = data;
});
$: changes_performed = !lodashIsEqual(original_data, editable_userdata);
$: groups_changed =
JSON.stringify(usergroups_array) ===
JSON.stringify(usergroups_array_original);
$: save_enabled =
(changes_performed || !groups_changed) && isEmail(editable_userdata.email);
function submit() {
if (data_loaded === true && save_enabled) {
editable_userdata.groups = usergroups_array;
Toastify({
text: $_("updating-user"),
duration: 2500,
}).showToast();
UserService.userControllerPut(original_data.id, editable_userdata)
.then((resp) => {
Object.assign(original_data, resp);
Object.assign(editable_userdata, resp);
original_data.permissions = resp.permissions;
usergroups_array = [];
resp.groups.forEach((g) => {
usergroups_array = usergroups_array.concat([g.id]);
});
usergroups_array_original = usergroups_array;
//
Toastify({
text: $_("user-updated"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
}
}
function deleteUser() {
UserService.userControllerRemove(original_data.id, true)
.then((resp) => {
location.replace("./");
})
.catch((err) => {});
}
</script>
{#await user_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 24 24"><path
fill="currentColor"
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>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">{$_('users')}</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">
{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}
<span data-id="user_actions_${editable_userdata.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:DELETE')}
{#if delete_triggered}
<button
on:click={deleteUser}
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-user')}</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="mt-3 text-sm w-full">
<p class="ml-1 font-medium text-gray-700">{$_('profile-picture')}</p>
<img
alt={$_('profile-picture')}
class="h-20 w-20 rounded-full overflow-hidden bg-gray-100"
src={editable_userdata.profilePic} />
</div>
<div class="mt-3 text-sm w-full">
<label
for="enabled"
class="ml-1 font-medium text-gray-700">{$_('active')}?</label>
<br />
<p class="text-gray-500">
<input
id="enabled"
on:change={() => {
editable_userdata.enabled = !editable_userdata.enabled;
}}
name="enabled"
type="checkbox"
checked={editable_userdata.enabled}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
{$_('set-the-user-active-inactive')}
</p>
</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"
bind:value={editable_userdata.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_userdata.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_userdata.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 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_userdata.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_userdata.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="username"
class="font-medium text-gray-700">{$_('username')}</label>
<input
autocomplete="off"
placeholder={$_('username')}
type="text"
bind:value={editable_userdata.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">
<span class="font-medium">{$_('groups')}</span>
<!-- svelte-ignore a11y-no-onchange -->
<select
bind:value={usergroups_array}
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"
multiple>
{#each allgroups as g}
{#if usergroups_array.includes(g.id)}
<option selected value={g.id}>{g.name}</option>
{:else}
<option value={g.id}>{g.name}</option>
{/if}
{/each}
</select>
</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="/users/{params.userid}/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}
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import isEmail from "validator/es/lib/isEmail";
import { UserService, UserGroupService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
export let params;
const user_promise = UserService.userControllerGetOne(params.userid);
let data_loaded = false;
let usergroups_array_original = [];
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;
$: original_data = {};
$: editable_userdata = {};
$: allgroups = [];
$: allgroups_ids = [];
$: usergroups_array = [];
$: search_permission = "";
user_promise.then((data) => {
let current_target = "";
let colorindex = -1;
// alphabetically sort permissions for color compatibility for target
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_userdata = data;
data.groups.forEach((g) => {
usergroups_array = usergroups_array.concat([g.id]);
});
usergroups_array_original = usergroups_array;
allgroups.forEach((g) => {
allgroups_ids.push(g.id);
});
});
UserGroupService.userGroupControllerGetAll().then((data) => {
allgroups = data;
});
$: changes_performed = !(JSON.stringify(original_data) == JSON.stringify(editable_userdata));
$: groups_changed =
JSON.stringify(usergroups_array) ===
JSON.stringify(usergroups_array_original);
$: save_enabled =
(changes_performed || !groups_changed) && isEmail(editable_userdata.email);
function submit() {
if (data_loaded === true && save_enabled) {
editable_userdata.groups = usergroups_array;
Toastify({
text: $_("updating-user"),
duration: 2500,
}).showToast();
UserService.userControllerPut(original_data.id, editable_userdata)
.then((resp) => {
Object.assign(original_data, resp);
Object.assign(editable_userdata, resp);
original_data.permissions = resp.permissions;
usergroups_array = [];
resp.groups.forEach((g) => {
usergroups_array = usergroups_array.concat([g.id]);
});
usergroups_array_original = usergroups_array;
//
Toastify({
text: $_("user-updated"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
}
}
function deleteUser() {
UserService.userControllerRemove(original_data.id, true)
.then((resp) => {
location.replace("./");
})
.catch((err) => {});
}
</script>
{#await user_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 24 24"><path
fill="currentColor"
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>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">{$_('users')}</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">
{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}
<span data-id="user_actions_${editable_userdata.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:DELETE')}
{#if delete_triggered}
<button
on:click={deleteUser}
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-user')}</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="mt-3 text-sm w-full">
<p class="ml-1 font-medium text-gray-700">{$_('profile-picture')}</p>
<img
alt={$_('profile-picture')}
class="h-20 w-20 rounded-full overflow-hidden bg-gray-100"
src={editable_userdata.profilePic} />
</div>
<div class="mt-3 text-sm w-full">
<label
for="enabled"
class="ml-1 font-medium text-gray-700">{$_('active')}?</label>
<br />
<p class="text-gray-500">
<input
id="enabled"
on:change={() => {
editable_userdata.enabled = !editable_userdata.enabled;
}}
name="enabled"
type="checkbox"
checked={editable_userdata.enabled}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
{$_('set-the-user-active-inactive')}
</p>
</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"
bind:value={editable_userdata.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_userdata.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_userdata.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 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_userdata.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_userdata.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="username"
class="font-medium text-gray-700">{$_('username')}</label>
<input
autocomplete="off"
placeholder={$_('username')}
type="text"
bind:value={editable_userdata.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">
<span class="font-medium">{$_('groups')}</span>
<!-- svelte-ignore a11y-no-onchange -->
<select
bind:value={usergroups_array}
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"
multiple>
{#each allgroups as g}
{#if usergroups_array.includes(g.id)}
<option selected value={g.id}>{g.name}</option>
{:else}
<option value={g.id}>{g.name}</option>
{/if}
{/each}
</select>
</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="/users/{params.userid}/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}

View File

@ -6,6 +6,7 @@
"active": "Aktiv",
"add-donation": "Sponsoring erstellen",
"add-donor": "Sponsor:in erstellen",
"add-scan": "Scan erstellen",
"add-user-group": "Neue Gruppe erstellen",
"add-your-first-contact": "Erstelle den ersten Kontakt",
"add-your-first-donor": "Erstelle die erste Sponsor:in",
@ -15,8 +16,12 @@
"add-your-first-team": "Erstelle das erste Team",
"add-your-first-track": "Erstelle den ersten Track (Laufstrecke).",
"add-your-first-user": "Erstelle die erste Benutzer:in",
"add-your-fist-scan": "Füge deinene ersten Scan hinzu",
"adding-scan": "Scan wird hinzugefügt",
"address": "Adresse",
"address-is-required": "Du musst eine Adresse angeben",
"after-deletion-we-cant-restore-your-old-profile": "Nach der Löschung können auch die Admins dein Profil nicht wiederherstellen!",
"after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that": "Nach der Änderung wirst du abgemeldet - bitte melde dich dann mit deinem neuen Passwort an.",
"all-associated-donations-will-get-deleted-as-well": "Alle Sponsorings dieser Sponsor:in werden ebenfalls gelöscht",
"all-associated-runners-will-be-deleted-too": "Alle zugehörigen Läufer:innen werden auch gelöscht!",
"all-associated-teams-and-runners-will-be-deleted-too": "Alle assoziierten Teams und Läufer:innen werden auch gelöscht!",
@ -31,18 +36,23 @@
"cancel": "Abbrechen",
"cancel-delete": "Löschen abbrechen",
"cancel-keep-donor": "Abbrechen, Sponsor:in behalten",
"cancel-keep-my-profile": "Abbrechen, mein Profil behalten",
"cancel-keep-organization": "Abbrechen und Organisation bearbeiten",
"cancel-keep-team": "Abbrechen, Team behalten",
"cannot-reset-your-password-directly": "Schade. \nWir können das Passwort leider nicht direkt zurücksetzen.\nBitte sende uns eine Mail in der du deine Identität bestätigst.",
"change-your-password-here": "Hier kannst du dein Passwort ändern",
"changing-your-password": "Passwort wird geändert",
"city": "Stadt",
"close": "Schließen",
"configure-the-tracks-and-minimum-lap-times": "Bearbeite die Tracks und ihre minimale Rundenzeit",
"confirm": "Bestätigen",
"confirm-delete": "Löschung Bestätigen",
"confirm-delete-donor-with-all-donations": "Bestätigen, Sponsor:in mit allen Sponsorings löschen",
"confirm-delete-my-user-profile": "Bestätigung, mein Benutzerprofil löschen",
"confirm-delete-organization-and-associated-teams-runners": "Bestätugung, lösche die Organisation und alle zugehörigen Teams und Läufer:innen.",
"confirm-delete-team-and-associated-runners": "Bestätigung, lösche das Team mitsamt seinen Läufer:innen.",
"confirm-deletion": "Löschung Bestätigen",
"confirm-the-new-password": "Neues Passwort bestätigen",
"contact": "Kontakt",
"contact-deleted": "Kontakt gelöscht",
"contact-information": "Kontaktinformation",
@ -60,6 +70,8 @@
"create-a-new-fixed-donation": "Erstelle eine neue Festbetragsspende",
"create-a-new-organization": "Neue Organisation anlegen",
"create-a-new-runner": "Neue Läufer:in erstellen",
"create-a-new-scan-fixed-only": "Neuen Scan erstellen (nur mit Festdistanz)",
"create-a-new-scanstation": "Neue Station erstellen",
"create-a-new-team": "Erstelle ein neues Team",
"create-a-new-track": "Neuen Track erstellen",
"create-a-new-user": "Neue Benutzer:in anlegen",
@ -74,6 +86,7 @@
"csv_import__lastname": "Nachname",
"csv_import__middlename": "Mittelname",
"csv_import__team": "Team",
"danger-zone": "Gefahrenzone",
"dashboard-greeting": "Moin",
"dashboard-title": "Dashboard",
"datatable": {
@ -97,9 +110,13 @@
"delete-donor": "Sponsor:in löschen",
"delete-group": "Gruppe löschen",
"delete-organization": "Organisation löschen",
"delete-profile": "Profil löschen",
"delete-runner": "Läufer:in löschen",
"delete-scan": "Scan löschen",
"delete-station": "Station löschen",
"delete-team": "Team Löschen",
"delete-user": "Benutzer:in löschen",
"deleted-scan": "Scan wurde gelöscht",
"dependency_name": "Name",
"description": "Beschreibung",
"description-optional": "Beschreibung (optional)",
@ -108,6 +125,8 @@
"distance": "Distanz",
"distance-donation": "Sponsoring",
"distance-in-km": "Distanz (in KM)",
"distance-track": "Distanz (+Track)",
"do-you-really-want-to-delete-your-profile": "Möchtest du dein Profil wirklich löschen?",
"do-you-want-to-delete-the-organization-delete_org-name": "Möchtest du die Organisation {orgname} löschen?",
"do-you-want-to-delete-the-team-delete_team-name": "Möchtest du das Team {teamname} löschen?",
"do-you-want-to-delete-this-donor-with-all-related-donations": "Möchtest du diese Sponsor:in mit all ihren Sponsorings löschen?",
@ -128,14 +147,17 @@
"edit": "Bearbeiten",
"edit-permissions": "Berechtigungen bearbeiten",
"email_address_or_username": "E-Mail-Adresse/ Benutzername",
"enabled": "aktiviert",
"english": "Englisch",
"error_on_login": "😢Fehler beim Login",
"erteilte": "Direkt erteilte",
"everything-concerning-your-profile": "Alles zu deinem Profil",
"everything-is-more-fun-together": "Im Team macht's mehr Spaß 🏃‍♂️🏃‍♀️🏃‍♂️",
"faq": "FAQ",
"filter-by-organization-team": "Filtern nach Organisation / Team",
"first-name": "Vorname",
"first-name-is-required": "Vorname muss angegeben werden",
"first-scan-of-the-day": "Erster Scan des Tages",
"fixed-donation": "Festbetragsspende",
"forgot_password": "Passwort vergessen?",
"geerbte": "geerbte",
@ -156,6 +178,7 @@
"group-name-is-required": "Der Gruppenname muss angegeben werden.",
"group-updated": "Gruppe aktualisiert",
"groups": "Gruppen",
"groups-are-being-loaded": "Gruppen werden geladen",
"home": "Start",
"icon-image-credits": "Wir möchten uns außerdem für die verwendeten Icons und Bilder bedanken bei:",
"import-finished": "Import abgeschlossen",
@ -166,9 +189,11 @@
"inactive": "Inaktiv",
"installed-version": "Installierte Version",
"internal-error": "Interner Fehler",
"invalid": "Ungültig",
"invalid-mail-reset": "Das ist keine gültige E-Mail",
"laeufer-hinzufuegen": "Läufer:in hinzufügen",
"laeufer-importieren": "Läufer:innen importieren",
"laptime": "Rundenzeit",
"last-name": "Nachname",
"last-name-is-required": "Nachname muss angegeben werden",
"lfk-is-os": "Das \"Lauf für Kaya!\" Frontend ist (wie alle anderen Projekte für den \"LfK!\" auch) ein OpenSource Projekt.",
@ -177,7 +202,10 @@
"loading-contact-details": "Kontaktdaten werden geladen ...",
"loading-donation-details": "Lade Sponsoringdetails",
"loading-donor-details": "Lade Details",
"loading-group-detail": "Lade Gruppendetails...",
"loading-profile-data": "Lade Profildaten",
"loading-runners": "Läufer:innen werden geladen...",
"loading-station-details": "Lade Scanstation-Details ...",
"log_in": "Anmelden",
"log_in_to_your_account": "Bitte melde dich an",
"login_is_checked": "Login wird überprüft",
@ -190,9 +218,15 @@
"name": "Name",
"name-is-required": "Der Gruppenname muss angegeben werden",
"new-password": "Neues Passwort",
"no-contact-found": "Keine Kontakte gefunden",
"no-contact-selected": "Kein Kontakt ausgewählt",
"no-contact-specified": "Kein Kontakt angegeben",
"no-donors-found": "Keine Spender:innen gefunden",
"no-license-text-could-be-found": "Kein Lizenz-Text gefunden 😢",
"no-organization-or-team-found": "Keine Organisationen oder Teams gefunden",
"no-organization-specified": "Keine Organisation angegeben",
"no-organizations-found": "Keine Organisationen gefunden",
"no-runners-found": "Keine Läufer:innen gefunden",
"no-tracks-added-yet": "Es wurden noch keine Tracks erstellt.",
"organization": "Organisation",
"organization-added": "Organisation hinzugefügt",
@ -205,11 +239,13 @@
"orgs": "Organisationen",
"oss_credit_description": "Wir verwenden eine Menge Open Source-Software bei diesen Projekten und möchten uns bei den folgenden Projekten und Mitwirkenden bedanken, die dazu beitragen, Open Source großartig zu machen!",
"password": "Passwort",
"password-changed": "Passwort wurde aktualisiert!",
"password-is-required": "Passwort muss angegeben werden",
"password-reset-failed": "Passwort zurücksetzen ist fehlgeschlagen!",
"password-reset-in-progress": "Passwort wird zurückgesetzt...",
"password-reset-mail-sent": "Passwort-Reset Mail wurde an \"{usersEmail}\" geschickt.",
"password-reset-successful": "Passwort erfolgreich zurückgesetzt!",
"passwords-dont-match": "Die Passwörter stimmen nicht überein.",
"pdf-generation-failed": "PDF Generierung fehlgeschlagen!",
"pdf-successfully-generated": "PDF wurde erfolgreich generiert!",
"pdfs-successfully-generated": "Alle PDFs wurden generiert!",
@ -220,6 +256,7 @@
"please-provide-a-password": "Bitte gebe ein Passwort an...",
"please-provide-the-nessecary-information-to-add-a-new-donor": "Bitte mach die Notwendigen Angaben, um eine neue Sponsor:in zu erstellen",
"please-provide-the-nessecary-information-to-create-a-new-donation": "Bitte gebe alle für das Sponsoring notwendigen Daten an.",
"please-provide-the-nessecary-information-to-create-a-new-scan": "Bitte gebe alle notwendigen Informationen an, um einen neuen Scan zu erstellen.",
"please-provide-the-required-csv-xlsx-file": "Bitte eine CSV oder XLSX Datei hochladen.",
"please-provide-the-required-information-for-creating-a-new-user-group": "Bitte gebe alle für eine neue Gruppe notwendigen Informationen an.",
"please-provide-the-required-information-to-add-a-new-contact": "Bitte gebe alle nötigen Informationen an, im den neuen Kontakt zu erstellen.",
@ -231,7 +268,9 @@
"please-request-a-new-reset-mail": "Bitte eine neue Passwortreset-Mail anfordern...",
"privacy": "Datenschutz",
"privacy-loading": "Datenschutzerklärung lädt...",
"profile": "Profil",
"profile-picture": "Profilbild",
"profile-updated": "Profil wurde aktualisiert!",
"read-license": "Lizenz-Text lesen",
"receipt-needed": "Spendenquittung benötigt",
"repo_link": "Link",
@ -249,15 +288,29 @@
"runners-are-being-loaded": "Läufer:innen werden geladen ...",
"save": "Speichern",
"save-changes": "Änderungen speichern",
"scan-added": "Scan hinzugefügt",
"scan-is-being-updated": "Scan wird aktualisiert",
"scan-with-fixed-distance": "Scan mit Festdistanz",
"scans": "Scans",
"scans-are-being-loaded": "Scans werden geladen",
"scanstation": "Scanner Station",
"scanstations": "Scanner Stationen",
"scanstations-are-being-loaded": "Scannerstationen werden geladen...",
"search-for-an-organization-by-name-or-id": "Suche eine Organisation (via Name oder Id)",
"search-for-an-organization-or-team-by-name-or-id": "Suche eine Organisation oder ein Team (via Name oder Id)",
"search-for-donor-name-or-id": "Suche eine Spender:in (via Name oder Id)",
"search-for-permission": "Berechtigungen durchsuchen",
"search-for-runner-by-name-or-id": "Suche eine Läufer:in (via Name oder Id)",
"select-all": "Alle auswählen",
"select-language": "Sprache auswählen",
"send-a-mail-to-lfk-odit-services": "Sende eine Mail an lfk@odit.services",
"set-the-user-active-inactive": "Den Benutzer auf (in)aktiv setzen",
"settings": "Einstellungen",
"settings-for-your-profile": "Die Einstellungen deines Accounts",
"something-about-the-group": "Infos zur Gruppe",
"stats-are-being-loaded": "Die Statistiken werden geladen...",
"status": "Status",
"stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können",
"successful-password-reset": "Passwort erfolgreich zurückgesetzt!",
"team": "Team",
"team-detail-is-being-loaded": "Team wird geladen...",
@ -266,18 +319,22 @@
"teams": "Teams",
"teams-are-being-loaded": "Teams werden geladen ...",
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number": "Die angegebene Telefonnummer ist nicht korrekt. <br /> Bitte gebe eine Telefonnummer im internationalen Format an...",
"the-scans-distance-must-be-greater-than-0m": "Die Distanz muss größer als 0m sein.",
"there-are-no-contacts-added-yet": "Es wurden noch keine Kontakte hinzugefügt.",
"there-are-no-donors-yet": "Es gibt noch keine Sponsor:innen",
"there-are-no-groups-yet": "Es gibt noch keine Gruppen",
"there-are-no-organizations-added-yet": "Es wurden noch keine Organisationen hinzugefügt.",
"there-are-no-runners-added-yet": "Es wurden noch keine Läufer:innen hinzugefügt.",
"there-are-no-scans-yet": "Es gibt noch keine scans",
"there-are-no-teams-added-yet": "Es wurden noch keine Teams hinzugefügt.",
"there-are-no-users-added-yet": "Es wurden noch keine Benutzer hinzugefügt.",
"this-might-take-a-moment": "Das könnte einen kleinen Moment dauern",
"this-scanstation-is": "Diese Station ist",
"total-distance": "gelaufene Strecke",
"total-donation-amount": "Gesamtbetrag",
"total-donations": "Spendensumme",
"total-scans": "gesamte Scans",
"track": "Track",
"track-added": "Track hinzugefügt",
"track-data-is-being-loaded": "Trackdaten werden geladen",
"track-is-being-added": "Track wird hinzugefügt...",
@ -286,26 +343,33 @@
"track-name": "Trackname",
"track-name-must-not-be-empty": "Der Name muss angegeben werden",
"tracks": "Tracks",
"update-password": "Passwort ändern",
"updated-contact": "Kontakt aktualisiert!",
"updated-donor": "Sponsor:in wurde aktualisiert",
"updated-organization": "Organisation wurde aktualisiert",
"updated-scan": "Scan wurde aktualisiert",
"updateing-group": "Gruppe wird aktualisiert...",
"updating-organization": "Organisation wird aktualisiert",
"updating-permissions": "Berechtigungen werden aktualisiert...",
"updating-runner": "Läufer:in wird aktualisiert.",
"updating-user": "Benutzer:in wird aktualisiert...",
"updating-your-profile": "Profil wird aktualisiert...",
"user-added": "Benutzer hinzugefügt",
"user-groups": "Benutzergruppen",
"user-is-being-added": "Benutzer wird hinzugefügt ...",
"user-updated": "Benutzer:in wurde aktualisiert",
"username": "Benutzername",
"users": "Benutzer",
"valid": "Gültig",
"valid-city-is-required": "Du musst eine Stadt angeben",
"valid-email-is-required": "Es wird eine valide E-Mail Adresse benötigt",
"valid-international-phone-number-is-required": "Du musst eine Telefonnummer im internationalen Format angeben...",
"valid-zipcode-postal-code-is-required": "Du musst eine valide Postleitzahl angeben",
"verfuegbare": "Verfügbar",
"welcome_wavinghand": "Willkommen 👋",
"yes-i-copied-the-token": "Ja, ich habe den Token kopiert",
"you-are-going-to-loose-all-permissions-and-access-to-the-runner-system": "Du wirst all deine Berechtigungen und den Zugriff aufs Läufersystem verlieren!",
"you-can-now-use-your-new-password-to-log-in-to-your-account": "Du kannst dich jetzt mit deinem neuen Passwort anmelden! 🎉",
"you-have-to-provide-an-organization": "Du musst eine Organisation angeben",
"zip-postal-code": "Postleitzahl"
}

View File

@ -6,6 +6,7 @@
"active": "Active",
"add-donation": "Add donation",
"add-donor": "add donor",
"add-scan": "Add scan",
"add-user-group": "Add User Group",
"add-your-first-contact": "Add your first contact",
"add-your-first-donor": "add your first donor",
@ -15,8 +16,12 @@
"add-your-first-team": "Add your first team",
"add-your-first-track": "Add your first track.",
"add-your-first-user": "Add your first user",
"add-your-fist-scan": "Add your fist scan",
"adding-scan": "Adding Scan",
"address": "Address",
"address-is-required": "Address is required",
"after-deletion-we-cant-restore-your-old-profile": "After deletion we can't restore your old profile!",
"after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that": "After the update you'll get logged out - Please login with your new password after that.",
"all-associated-donations-will-get-deleted-as-well": "All associated donations will get deleted as well",
"all-associated-runners-will-be-deleted-too": "All associated runners will be deleted too!",
"all-associated-teams-and-runners-will-be-deleted-too": "All associated teams and runners will be deleted too!",
@ -31,18 +36,23 @@
"cancel": "Cancel",
"cancel-delete": "Cancel Delete",
"cancel-keep-donor": "Cancel, keep donor",
"cancel-keep-my-profile": "Cancel, keep my profile",
"cancel-keep-organization": "Cancel, keep organization",
"cancel-keep-team": "Cancel, keep team",
"cannot-reset-your-password-directly": "Bummer. We unfortunately cannot reset your password directly. Please send us a mail and confirm your identity",
"change-your-password-here": "Change your password here",
"changing-your-password": "Changing your password",
"city": "City",
"close": "Close",
"configure-the-tracks-and-minimum-lap-times": "configure the tracks & minimum lap times",
"confirm": "Confirm",
"confirm-delete": "Confirm Delete",
"confirm-delete-donor-with-all-donations": "Confirm, delete donor with all donations",
"confirm-delete-my-user-profile": "Confirm, delete my user profile",
"confirm-delete-organization-and-associated-teams-runners": "Confirm, delete organization and associated teams+runners.",
"confirm-delete-team-and-associated-runners": "Confirm, delete team and associated runners.",
"confirm-deletion": "Confirm Deletion",
"confirm-the-new-password": "Confirm the new password",
"contact": "Contact",
"contact-deleted": "Contact deleted",
"contact-information": "Contact Information",
@ -60,6 +70,8 @@
"create-a-new-fixed-donation": "Create a new fixed donation",
"create-a-new-organization": "Create a new Organization",
"create-a-new-runner": "Create a new Runner",
"create-a-new-scan-fixed-only": "Create a new scan (fixed only)",
"create-a-new-scanstation": "Create a new station",
"create-a-new-team": "Create a new team",
"create-a-new-track": "Create a new Track",
"create-a-new-user": "Create a new User",
@ -74,6 +86,7 @@
"csv_import__lastname": "Lastname",
"csv_import__middlename": "Middlename",
"csv_import__team": "Team",
"danger-zone": "Danger zone",
"dashboard-greeting": "hello there",
"dashboard-title": "Dashboard",
"datatable": {
@ -97,9 +110,13 @@
"delete-donor": "Delete donor",
"delete-group": "Delete Group",
"delete-organization": "Delete Organization",
"delete-profile": "Delete Profile",
"delete-runner": "Delete Runner",
"delete-scan": "Delete scan",
"delete-station": "Delete station",
"delete-team": "Delete Team",
"delete-user": "Delete User",
"deleted-scan": "Deleted scan",
"dependency_name": "Name",
"description": "description",
"description-optional": "Description (optional)",
@ -108,6 +125,8 @@
"distance": "Distance",
"distance-donation": "distance donation",
"distance-in-km": "Distance in km",
"distance-track": "Distance (+Track)",
"do-you-really-want-to-delete-your-profile": "Do you really want to delete your profile?",
"do-you-want-to-delete-the-organization-delete_org-name": "Do you want to delete the organization {orgname}?",
"do-you-want-to-delete-the-team-delete_team-name": "Do you want to delete the team {teamname}?",
"do-you-want-to-delete-this-donor-with-all-related-donations": "Do you want to delete this donor with all related donations",
@ -128,14 +147,17 @@
"edit": "Edit",
"edit-permissions": "edit permissions",
"email_address_or_username": "Email / username",
"enabled": "enabled",
"english": "English",
"error_on_login": "Error on login",
"erteilte": "Directly granted",
"everything-concerning-your-profile": "Everything concerning your profile",
"everything-is-more-fun-together": "everything is more fun together 🏃‍♂️🏃‍♀️🏃‍♂️",
"faq": "FAQ",
"filter-by-organization-team": "Filter by Organization/ Team",
"first-name": "First name",
"first-name-is-required": "First Name is required",
"first-scan-of-the-day": "First scan of the day.",
"fixed-donation": "fixed donation",
"forgot_password": "Forgot your password?",
"geerbte": "inherited",
@ -156,6 +178,7 @@
"group-name-is-required": "Group name is required",
"group-updated": "group updated",
"groups": "Groups",
"groups-are-being-loaded": "Groups are being loaded",
"home": "Home",
"icon-image-credits": "We also want to thank these projects for illustrations and icons:",
"import-finished": "Import finished",
@ -166,9 +189,11 @@
"inactive": "Inactive",
"installed-version": "Installed version",
"internal-error": "Internal Error",
"invalid": "Invalid",
"invalid-mail-reset": "the provided email is invalid",
"laeufer-hinzufuegen": "Add runner",
"laeufer-importieren": "Läufer importieren",
"laptime": "Laptime",
"last-name": "Last name",
"last-name-is-required": "Last Name is required",
"lfk-is-os": "The \"Lauf für Kaya!\" Frontend is (like all other projects for the \"LfK!\" Also) an open source project.",
@ -177,7 +202,10 @@
"loading-contact-details": "Loading contact details...",
"loading-donation-details": "Loading donation details",
"loading-donor-details": "Loading donor details",
"loading-group-detail": "Loading group detail...",
"loading-profile-data": "Loading profile data",
"loading-runners": "loading runners...",
"loading-station-details": "Loading station details",
"log_in": "Log in",
"log_in_to_your_account": "Log in to your account",
"login_is_checked": "Login is being checked...",
@ -190,9 +218,15 @@
"name": "Name",
"name-is-required": "Name is required",
"new-password": "New password",
"no-contact-found": "No contacts found",
"no-contact-selected": "No contact selected",
"no-contact-specified": "no contact specified",
"no-donors-found": "No donors found",
"no-license-text-could-be-found": "No license text could be found 😢",
"no-organization-or-team-found": "No organization or team found",
"no-organization-specified": "no organization specified",
"no-organizations-found": "No organizations found",
"no-runners-found": "No runners found",
"no-tracks-added-yet": "there are no tracks added yet.",
"organization": "Organization",
"organization-added": "Organization added",
@ -202,14 +236,16 @@
"organization-name-is-required": "Organization name is required",
"organizations": "Organizations",
"organizations-are-being-loaded": "organizations are being loaded...",
"orgs": "Orgs",
"orgs": "Organizations",
"oss_credit_description": "We use a lot of open source software on these projects, and would like to thank the following projects and contributors who help make open source great!",
"password": "Password",
"password-changed": "Password changed!",
"password-is-required": "Password is required",
"password-reset-failed": "Password reset failed!",
"password-reset-in-progress": "Password Reset in Progress...",
"password-reset-mail-sent": "Password reset mail was sent to \"{usersEmail}\".",
"password-reset-successful": "Password Reset successful!",
"passwords-dont-match": "Passwords don't match",
"pdf-generation-failed": "PDF generation failed!",
"pdf-successfully-generated": "PDF successfully generated!",
"pdfs-successfully-generated": "PDFs successfully generated!",
@ -220,6 +256,7 @@
"please-provide-a-password": "Please provide a password...",
"please-provide-the-nessecary-information-to-add-a-new-donor": "Please provide the nessecary information to add a new donor",
"please-provide-the-nessecary-information-to-create-a-new-donation": "Please provide the nessecary information to create a new donation",
"please-provide-the-nessecary-information-to-create-a-new-scan": "Please provide the nessecary information to create a new scan.",
"please-provide-the-required-csv-xlsx-file": "Please provide the required csv/ xlsx file",
"please-provide-the-required-information-for-creating-a-new-user-group": "Please provide the required information for creating a new user group.",
"please-provide-the-required-information-to-add-a-new-contact": "Please provide the required information to add a new contact.",
@ -231,7 +268,9 @@
"please-request-a-new-reset-mail": "Please request a new reset mail...",
"privacy": "Privacy",
"privacy-loading": "Privacy loading...",
"profile": "Profile",
"profile-picture": "Profile Picture",
"profile-updated": "Profile updated!",
"read-license": "Read License",
"receipt-needed": "Receipt needed",
"repo_link": "Link",
@ -249,15 +288,29 @@
"runners-are-being-loaded": "runners are being loaded...",
"save": "Save",
"save-changes": "Save Changes",
"scan-added": "Scan added",
"scan-is-being-updated": "Scan is being updated",
"scan-with-fixed-distance": "Scan with fixed distance",
"scans": "Scans",
"scans-are-being-loaded": "Scans are being loaded",
"scanstation": "Scanstation",
"scanstations": "Scanstations",
"scanstations-are-being-loaded": "Loading scanstations...",
"search-for-an-organization-by-name-or-id": "Search for an organization (by name or id)",
"search-for-an-organization-or-team-by-name-or-id": "Search for an organization or team (by name or id)",
"search-for-donor-name-or-id": "Search for donor (by name or id)",
"search-for-permission": "Search for permission",
"search-for-runner-by-name-or-id": "Search for runner (by name or id)",
"select-all": "select all",
"select-language": "Select language",
"send-a-mail-to-lfk-odit-services": "send a mail to lfk@odit.services",
"set-the-user-active-inactive": "set the user active/ inactive",
"settings": "Settings",
"settings-for-your-profile": "Settings for your profile",
"something-about-the-group": "Something about the group...",
"stats-are-being-loaded": "stats are being loaded...",
"status": "Status",
"stuff-that-could-harm-your-profile": "Stuff that could harm your profile",
"successful-password-reset": "Successful password reset!",
"team": "Team",
"team-detail-is-being-loaded": "team detail is being loaded...",
@ -266,18 +319,22 @@
"teams": "Teams",
"teams-are-being-loaded": "teams are being loaded...",
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number": "the provided phone number is invalid.<br />please enter a valid international number...",
"the-scans-distance-must-be-greater-than-0m": "The scan's distance must be greater than 0m",
"there-are-no-contacts-added-yet": "There are no contacts added yet.",
"there-are-no-donors-yet": "There are no donors yet",
"there-are-no-groups-yet": "There are no groups yet",
"there-are-no-organizations-added-yet": "There are no organizations added yet.",
"there-are-no-runners-added-yet": "There are no runners added yet.",
"there-are-no-scans-yet": "There are no scans yet",
"there-are-no-teams-added-yet": "There are no teams added yet.",
"there-are-no-users-added-yet": "There are no users added yet.",
"this-might-take-a-moment": "This might take a moment 👀",
"this-scanstation-is": "This scanstation is",
"total-distance": "total distance",
"total-donation-amount": "total donation amount",
"total-donations": "total donations",
"total-scans": "total scans",
"track": "Track",
"track-added": "Track added",
"track-data-is-being-loaded": "Track data is being loaded",
"track-is-being-added": "Track is being added...",
@ -286,26 +343,33 @@
"track-name": "Track name",
"track-name-must-not-be-empty": "Track name must not be empty",
"tracks": "Tracks",
"update-password": "Update password",
"updated-contact": "Updated contact!",
"updated-donor": "updated donor",
"updated-organization": "updated organization",
"updated-scan": "updated scan",
"updateing-group": "updateing group...",
"updating-organization": "updating organization",
"updating-permissions": "updating permissions...",
"updating-runner": "Updating runner...",
"updating-user": "updating user...",
"updating-your-profile": "Updating your profile...",
"user-added": "User added",
"user-groups": "User Groups",
"user-is-being-added": "User is being added...",
"user-updated": "User updated",
"username": "Username",
"users": "Users",
"valid": "Valid",
"valid-city-is-required": "Valid city is required",
"valid-email-is-required": "valid email is required",
"valid-international-phone-number-is-required": "valid international phone number is required...",
"valid-zipcode-postal-code-is-required": "Valid zipcode/ postal code is required",
"verfuegbare": "availdable",
"welcome_wavinghand": "Welcome 👋",
"yes-i-copied-the-token": "Yes, I copied the token",
"you-are-going-to-loose-all-permissions-and-access-to-the-runner-system": "You are going to loose all permissions and access to the runner system!",
"you-can-now-use-your-new-password-to-log-in-to-your-account": "You can now use your new password to log in to your account! 🎉",
"you-have-to-provide-an-organization": "You have to provide an organization",
"zip-postal-code": "ZIP/ postal code"
}