Compare commits

...

205 Commits

Author SHA1 Message Date
76b69d851a 🚀RELEASE v0.15.3
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-16 18:22:27 +02:00
224f586368 Small bugfix (null got displayed) 🛠 2021-04-16 18:22:00 +02:00
9add6c8ff1 🚀RELEASE v0.15.2
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-16 18:10:50 +02:00
7a63d4eed1 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-16 18:09:37 +02:00
e54a4807f7 NGINX cache assets 2021-04-16 18:09:30 +02:00
cee04c1d6f Footer - noopener link 2021-04-16 18:09:22 +02:00
cbec78589d Hotfix: Team change recognition 🐞
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-16 18:06:53 +02:00
a85db7cb3f 🚀RELEASE v0.15.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-16 17:53:38 +02:00
2bd3779839 Merge pull request '🐞🐳 fix Dockerfile' (#138) from bugfix/136-opacity_reactivity into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #138
2021-04-16 15:51:37 +00:00
303e33cafb 🐞🐳 fix Dockerfile
ref #136
2021-04-16 17:46:15 +02:00
b4e689dddf Dockerfile now uses selfhosted registry
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-15 19:50:37 +02:00
98a0b036c5 new license file version [CI SKIP] 2021-04-15 17:46:04 +00:00
fb3f30fb10 Merge pull request 'Opacity import fix bugfix/136-opacity_reactivity' (#137) from bugfix/136-opacity_reactivity into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #137
2021-04-15 17:44:49 +00:00
6213952007 Added bs import fix
ref #136
2021-04-15 19:43:43 +02:00
07ac041d69 🚚 move to tailwind
ref #136
2021-04-15 19:22:57 +02:00
5c02028841 🚀RELEASE v0.15.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-15 18:30:38 +02:00
c561b53670 Merge pull request 'Mark donations as payed feature/133-donation_payments' (#135) from feature/133-donation_payments into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #135
2021-04-15 16:29:39 +00:00
dcd0d5a362 Merge branch 'feature/133-donation_payments' of https://git.odit.services/lfk/frontend into feature/133-donation_payments 2021-04-15 18:27:47 +02:00
18acac83bc AddDonationModal - vertical alignment for paid status
ref #133
2021-04-15 18:27:35 +02:00
d7d44470bb DonationsOverview contrast on action
ref #133
2021-04-15 18:25:52 +02:00
0f0aae7ba4 Fixed chante recognition bug for fixed donation
ref #133
2021-04-15 18:21:23 +02:00
4c0886a5d9 Fixed typo
ref #133
2021-04-15 18:04:03 +02:00
04a3038369 Added missing updated comparison
ref #133
2021-04-15 16:56:03 +02:00
bdcf5d3fc0 Added payment updating via detail
ref #133
2021-04-15 15:54:14 +02:00
c7a858eed7 Sorted translations
ref #133
2021-04-15 15:42:47 +02:00
de5aa9237d Added **all** missing toast translations
ref #133
2021-04-15 15:42:29 +02:00
d015f97395 Added translations 🌎
ref #113
2021-04-15 15:34:36 +02:00
57618156b4 Added msiisng runner id conversion
ref #133
2021-04-15 15:30:23 +02:00
865254d646 Fixed styling
ref #133
2021-04-15 15:25:17 +02:00
1dbab03fe7 You can now add payments from the donation overview
ref #133
2021-04-15 15:24:31 +02:00
a943aaf5fc You can now open a modal to add a payment to a donation from the donation overview
ref #133
2021-04-15 15:05:05 +02:00
6e6e8b2617 Added Add Payment button to donor overview
ref #133
2021-04-15 14:40:46 +02:00
4c2c24af2c Changed top info style for donation overview
ref #133
2021-04-15 14:33:35 +02:00
3d3a10aafb You can now mark fixed donations as already paid on creation
ref #133
2021-04-15 14:31:24 +02:00
000fc97beb Changed top info style for donation detail
ref #133
2021-04-15 14:18:28 +02:00
5645eeaafa Added paid donation amount and status to donation detail
ref #133
2021-04-15 14:17:28 +02:00
961477d522 Added total donation amount to donation overview
ref #133
2021-04-15 14:12:11 +02:00
a5f71015a6 Added total donation amount to donor detail
ref #133
2021-04-15 14:10:35 +02:00
e42ea943b7 Added total donation amount to donor overview
ref #133
2021-04-15 14:09:23 +02:00
9c5fc6b61c 🚀RELEASE v0.14.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-14 19:28:00 +02:00
302caf015f new license file version [CI SKIP] 2021-04-14 17:27:30 +00:00
e11296071a Merge pull request 'added donor receipt list download to DonorsOverview' (#134) from feature/132-export-donors-receipt-list into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #134
close #132
2021-04-14 17:26:10 +00:00
112eb29f93 Switched to selfhosted images
ref #132
2021-04-14 19:24:06 +02:00
c6c97516b3 Sorted translations 🌎
ref #132
2021-04-14 19:23:26 +02:00
03676b2894 Fixed typos in translations
ref #132
2021-04-14 19:23:07 +02:00
9ca57fac2e bump @odit/lfk-client-js@0.11.0
ref #132
2021-04-14 18:58:57 +02:00
18f151c1fb general version bump
ref #132
2021-04-14 18:57:14 +02:00
e90e56d8b2 replace donationAmount with paidDonationAmount
ref #132
2021-04-14 18:54:05 +02:00
d241ca5698 added donor receipt list download to DonorsOverview
ref #132
2021-04-14 18:43:51 +02:00
b512cf8667 🚀RELEASE v0.13.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-11 21:14:03 +02:00
a24d2923c6 For await fix
Some checks failed
continuous-integration/drone/push Build is failing
2021-04-11 21:13:44 +02:00
467808abef 🚀RELEASE v0.13.0
Some checks failed
continuous-integration/drone/push Build is failing
2021-04-11 21:08:58 +02:00
861f1f2216 Merge pull request 'Better org pdf generation feature/130-org_doc_splitting' (#131) from feature/130-org_doc_splitting into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #131
2021-04-11 19:07:53 +00:00
509b22bea0 Org certificate generation now runs in sequence
ref #130
2021-04-11 21:00:33 +02:00
7447b2f4c1 Fixed const -> let
ref #130
2021-04-11 20:54:38 +02:00
fef14b6e4f Org card generation now runs in sequence
ref #130
2021-04-11 20:53:58 +02:00
01d2a7e6aa Org contract generation now runs in sequence
ref #130
2021-04-11 20:48:20 +02:00
ac586fec5a Hotfix: Org * generation🐞
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-11 20:17:34 +02:00
5476808683 Emergency document server url change
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-11 20:10:08 +02:00
331d737796 🚀RELEASE v0.12.5
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-08 19:30:20 +02:00
ef81b8adf9 Merge pull request 'Added runner team's parentorg name to runenr overciew' (#129) from feature/128-runner_orgs into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #129
2021-04-08 17:29:27 +00:00
8a7d635cef Switched to html entity
ref #128
2021-04-08 18:00:47 +02:00
4c259c1eef Added runner team's parentorg name to runenr overciew
ref #128
2021-04-08 17:58:49 +02:00
5b4ede5e2f 🚀RELEASE v0.12.4
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-08 17:34:32 +02:00
d0ab3dda78 🚑 [HOTFIX] - drop "svelte-infinite-loading"
Some checks failed
continuous-integration/drone/push Build is failing
2021-04-08 17:34:04 +02:00
d9cf51b4bb 🚀RELEASE v0.12.3
Some checks failed
continuous-integration/drone/push Build is failing
2021-04-08 17:30:23 +02:00
aa17f24220 new license file version [CI SKIP] 2021-04-08 15:28:50 +00:00
cf60edf7d4 Merge pull request 'fix' (#126) from bugfix/125-mobile into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #126
close #125
2021-04-08 15:27:22 +00:00
ffbc243194 custom css fix for collapsed_navigation
ref #125
2021-04-07 21:44:29 +02:00
b6b07cf30c 🐞 bugfix for svelte x tailwind class names
ref #125
2021-04-07 21:35:01 +02:00
495a6b22bd almost fixed... 2021-04-07 21:28:21 +02:00
0acaffbdfa fix
ref #125
2021-04-07 20:25:04 +02:00
6043bc4517 🚀RELEASE v0.12.2
Some checks failed
continuous-integration/drone/push Build is failing
2021-04-07 20:11:21 +02:00
e6ed066e3f Merge pull request 'feature/110-virtual_list' (#124) from feature/110-virtual_list into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #124
2021-04-07 18:10:02 +00:00
ee4e8655b8 Merge branch 'dev' into feature/110-virtual_list 2021-04-07 20:09:35 +02:00
37970d2be6 pre-merge fixes
ref #110
2021-04-07 18:59:46 +02:00
1376788016 updated virtual scroll list 2021-04-07 18:38:52 +02:00
4cad86cf85 fixed height table 2021-04-07 18:19:58 +02:00
6304116edb wip on virtuallist 2021-04-06 22:16:24 +02:00
834ff8fa63 🚀RELEASE v0.12.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-06 16:47:37 +02:00
1f428a535e Merge pull request 'ImportRunnerModal Cancel Button feature/122-import_cancel' (#123) from feature/112-import_cancel into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #123
2021-04-06 14:42:37 +00:00
0c40966970 Added cancel button for the first stage of runner import
ref #112
2021-04-05 16:23:24 +02:00
9da071fe9b Escape now triggers foll modal close (including reset) instead of just hiding th modal
ref #112
2021-04-05 16:14:43 +02:00
892a04f289 🚀RELEASE v0.12.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-05 16:09:17 +02:00
27cc9727f1 Fixed package version
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-05 16:08:41 +02:00
f0738d451b Merge pull request 'Implmented certificate generation feature/119-Certificate_generation' (#120) from feature/119-Certificate_generation into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #120
2021-04-05 14:05:38 +00:00
9e6a8daf2c Sorted translations 🌍
ref #119
2021-04-05 16:04:44 +02:00
bfacfec765 The PFS Prefixes now get translated via i18n
ref #119
2021-04-05 16:04:26 +02:00
0bae5bf32b sponsoring pdf names now include their locale
ref #119
2021-04-05 15:37:12 +02:00
22b09d16d0 Cleaned up generation strings and added the schem for single runner generations for sponsoring contracts
ref #119
2021-04-05 15:36:01 +02:00
9c867e106e Cleaned up generation strings and added the schem for single runner generations for cards
ref #119
2021-04-05 15:33:55 +02:00
304f28a3c1 certificate pdf names now include their locale
ref #119
2021-04-05 15:31:52 +02:00
d65d3793de Changed the basic nameing generation for runenr certificate files
ref #119
2021-04-05 15:31:01 +02:00
3638d87bd2 Runnercard pdfs now include their locale
ref #119
2021-04-05 15:28:13 +02:00
b97a92860d Fixed wrong permissiong getting checked
ref #119
2021-04-05 15:24:42 +02:00
7c86a5eeb3 added missing/ wrong translations + formatting!
ref #119
2021-04-05 12:02:18 +02:00
d23dbaaf69 Removed useless console.log
ref #119
2021-04-03 20:05:27 +02:00
e6ffc371e1 Certificate generation from org detail
ref #119
2021-04-03 20:03:57 +02:00
3177c6eaa3 Certificate generation from org overview
ref #119
2021-04-03 20:02:52 +02:00
acd2f0519d Certificate generation from team detail
ref #119
2021-04-03 20:00:51 +02:00
18ec100c33 Certificate generation from team overview
ref #119
2021-04-03 19:59:34 +02:00
fa55fce76e Merge branch 'feature/119-Certificate_generation' of git.odit.services:lfk/frontend into feature/119-Certificate_generation 2021-04-03 19:59:20 +02:00
f47d5e347d Copy-paste fix
ref #119
2021-04-03 19:59:18 +02:00
7488a8b597 Copy-paste fix
ref #119
2021-04-03 19:58:24 +02:00
2e3ac154be Implemented generation for orgs
ref #119
2021-04-03 19:52:41 +02:00
2472640755 Implemented generation for teams
ref #119
2021-04-03 19:51:01 +02:00
7b685d6cad Added certificate generation from runner overview and detail
ref #119
2021-04-03 19:48:31 +02:00
17f6f4e616 Added i18n
ref #119
2021-04-03 19:46:17 +02:00
48cfc15cfb Removed useless console log
ref #119
2021-04-03 19:44:57 +02:00
bb9b779cee You can now generate certificates from the runner overview
ref #119
2021-04-03 19:44:26 +02:00
af63ce67ae Added basic certificate generation component
ref #119
2021-04-03 19:38:54 +02:00
5cc4871ec4 new license file version [CI SKIP] 2021-04-03 17:18:28 +00:00
c8cfe669b8 Merge pull request 'feature/108_vite_migration' (#118) from feature/108_vite_migration into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #118
close #108
2021-04-03 17:17:23 +00:00
8b74d6d759 bump @odit/lfk-client-js@0.10.1
ref #108
2021-04-03 19:16:53 +02:00
a9227768de 🐞 fix await for esNext
ref #108
2021-04-03 19:13:05 +02:00
d966e1d4de Merge branch 'dev' into feature/108_vite_migration
# Conflicts:
#	index.html
#	package.json
#	public/licenses.json
#	src/App.svelte
#	src/components/orgs/OrgOverview.svelte
#	src/components/teams/TeamsOverview.svelte
2021-04-03 19:10:10 +02:00
ceb2146c1b 🔨 dev container open
ref #108
2021-04-03 18:31:03 +02:00
8d006d8c74 version bump: vite-plugin-windicss@0.12.2
ref #108
2021-04-03 18:22:00 +02:00
777304f259 🔨🔥 alpine based devcontainer with working yarn PnP
ref #108
2021-04-02 21:57:56 +02:00
12433f7c23 🧹 reorder + fix package
ref #108
2021-04-02 21:56:57 +02:00
44b53da345 🚚 move @svitejs/vite-plugin-svelte to @sveltejs/vite-plugin-svelte
ref #108
2021-04-02 21:47:43 +02:00
ab45fc144e upgrade vite-plugin-windicss@0.12.1
ref #108
2021-04-02 21:20:48 +02:00
e99e9e0708 update licenses.json
ref #108
2021-04-02 21:20:05 +02:00
467404bfc8 🐞 fix main.js linking
ref #108
2021-04-02 21:19:49 +02:00
ce50fa2a62 🧹 drop unused dependencies
ref #108
2021-04-02 21:19:29 +02:00
10a011d842 🐞 fix vite config for production system
ref #108
2021-04-02 21:07:16 +02:00
5352410d0c 🐞 fix NGINX config
ref #108
2021-04-02 21:06:57 +02:00
c5d155396a 💾 prevent env.js from being cached
ref #108
2021-04-01 19:35:27 +02:00
93187099d3 🔨 re-added VS Code devcontainer config
ref #108
2021-04-01 19:35:10 +02:00
aa24b1dce5 📃 added readme
ref #108
2021-04-01 19:32:10 +02:00
eb3ede9593 fix dev script
ref #108
2021-04-01 19:30:31 +02:00
d7fecfbd0b version bumps
ref #108
2021-04-01 19:30:15 +02:00
b065b4ff21 📍 version bump + pin
ref #108
2021-03-30 18:36:20 +02:00
87370d24be Merge branch 'dev' of git.odit.services:lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-30 18:29:03 +02:00
8f8b9988ad new license file version [CI SKIP] 2021-03-30 16:29:19 +00:00
f8ccf4f5d8 🚀RELEASE v0.11.0 2021-03-30 18:28:53 +02:00
25d8b86efd Merge pull request 'Generate and print bulk blank cards feature/116-download_blanc_cards' (#117) from feature/116-download_blanc_cards into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #117
2021-03-30 16:27:48 +00:00
0cd3e937d8 bump vite to 2.1.3
ref #108
2021-03-30 18:21:18 +02:00
89bb9c215e Sorted translations
ref #116
2021-03-29 18:52:25 +02:00
2d18686ce7 Bumped lfk client js version
ref #116
2021-03-29 18:52:10 +02:00
1d999d4910 Now returning cards on creation with pdf
ref #116
2021-03-29 18:23:17 +02:00
7dfaa7579a Bumped lfk-client-js
ref #116
2021-03-29 18:15:00 +02:00
08cb079e97 Fixed button styling
ref #116
2021-03-29 17:57:34 +02:00
450aa83592 Merge branch 'feature/116-download_blanc_cards' of git.odit.services:lfk/frontend into feature/116-download_blanc_cards
# Conflicts:
#	src/components/cards/AddCardBulkModal.svelte
2021-03-29 17:47:18 +02:00
0614c76e92 Added button (including translations
ref #116
2021-03-29 17:46:56 +02:00
97e338f9d4 Added button (including translations
ref #116
2021-03-29 17:46:51 +02:00
636f018daa Added comment
ref #116
2021-03-29 17:44:59 +02:00
c8d639024a Added function for generating cards with pdf
ref #116
2021-03-29 17:44:30 +02:00
6be2ee626a package cleanup 2021-03-26 21:22:46 +01:00
f7fc1967a5 🚀RELEASE v0.10.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-26 20:07:14 +01:00
aedb8a765b new license file version [CI SKIP] 2021-03-26 19:06:59 +00:00
cf58bd15c3 Bumped lfk-client version 🔝
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-26 20:05:26 +01:00
34f4f68524 new license file version [CI SKIP] 2021-03-26 19:04:28 +00:00
09b8144080 Merge pull request 'Implemented password strength test feature/106-password_strength' (#115) from feature/106-password_strength into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #115
2021-03-26 19:03:03 +00:00
f1e6fb4ce7 Merge branch 'dev' into feature/106-password_strength 2021-03-26 19:59:47 +01:00
2ca63fd1f6 🚀RELEASE v0.9.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-26 19:59:29 +01:00
a5d25e7d92 Merge pull request 'Org selfservice Link feature/112-org_registration_links' (#114) from feature/112-org_registration_links into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #114
2021-03-26 18:58:34 +00:00
4167819e7a Formatting🛠
ref #106
2021-03-26 19:52:31 +01:00
5bd3a463f0 Sorted translations 🌍
ref #106
2021-03-26 19:51:57 +01:00
79c447b4c6 Added translations
ref #106
2021-03-26 19:51:27 +01:00
540304f947 User creation can now only be triggered if pw is strong enoug
ref #106
2021-03-26 19:48:42 +01:00
75d8f7331b Reset can now only be triggered if pw is strong enoug
ref #106
2021-03-26 19:47:26 +01:00
b2509e9e53 Module now exports functions that check if a password is strong enough and equal to a potential confirmation field
ref #106
2021-03-26 19:45:53 +01:00
7862f44653 Now using pw strength component for user creation
ref #106
2021-03-26 19:31:21 +01:00
962dd0c1bb Added missing exports
ref #106
2021-03-26 19:29:47 +01:00
5d5f7c7f5c Now using pw strength component for reset
ref #106
2021-03-26 19:29:37 +01:00
6aaf838451 Now using pw strength component
ref #106
2021-03-26 19:29:25 +01:00
ad3bd312e9 Added a password strength verification
ref #106
2021-03-26 19:26:26 +01:00
5fa9939696 Added more cirteria to the password strength component
ref #106
2021-03-26 19:02:09 +01:00
4956bb0e9c Implemented a custom password strength component
ref #106
2021-03-26 18:47:24 +01:00
c074c12be7 Sorted translations
ref #112
2021-03-26 18:11:49 +01:00
ddbc293e9c Added translations
ref #112
2021-03-26 18:11:23 +01:00
a3921b45c7 Copy now 100% worX
ref #112
2021-03-26 18:11:10 +01:00
38e1c8c5a1 Merge branch 'feature/112-org_registration_links' of git.odit.services:lfk/frontend into feature/112-org_registration_links 2021-03-26 18:04:08 +01:00
c2d29ff233 Added check if key exists
ref #112
2021-03-26 18:04:05 +01:00
2316baa898 Added check if key exists 2021-03-26 18:03:58 +01:00
f185d559c0 Formatting
ref #112
2021-03-26 18:01:34 +01:00
73d95bc004 Fixed changes in wrong file
ref #112
2021-03-26 18:01:17 +01:00
fcd55f89d7 You can now copy the selfservice links to your clipboard
ref #112
2021-03-26 17:59:46 +01:00
f9fe793573 Added checkbox to enable registration
ref #112
2021-03-26 17:37:54 +01:00
bc36411993 Merge branch 'main' into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-26 16:17:51 +00:00
48506236bf Merge branch 'dev' of git.odit.services:lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-26 17:15:51 +01:00
ded9b589fe new license file version [CI SKIP] 2021-03-26 16:16:10 +00:00
67c3732fad 🚀RELEASE v0.9.0 2021-03-26 17:15:42 +01:00
2932f4591e Merge pull request 'Runner cards feature/94-runnercard_mgnt' (#111) from feature/94-runnercard_mgnt into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #111
2021-03-26 16:14:45 +00:00
df53c07450 CardsOverview - move to 'enabled' language key
ref #94
2021-03-26 17:13:55 +01:00
40899e9d80 drop console log - CardDetailModal
ref #94
2021-03-26 17:13:38 +01:00
f794af0950 ✒ typo - "Geb" -> "Gebe"
ref #94
2021-03-26 17:01:40 +01:00
1665a1a093 Sorted translations 🌍
ref #94
2021-03-26 16:22:10 +01:00
4a36fb6d95 Added card generation/printing from detail
ref #94
2021-03-26 16:21:46 +01:00
acf78a8822 Added a new runenrcard logo
ref #94
2021-03-26 16:12:40 +01:00
f5c1ec9939 Fixed counting bug
ref #94
2021-03-26 16:05:49 +01:00
4b3d38b05b Now with working org runenrcard generation
ref #94
2021-03-26 16:05:39 +01:00
23e0b53107 Added runnercard generation for teams
ref #94
2021-03-26 15:58:39 +01:00
c907486c4d Working runner runnercard generation
ref #94
2021-03-26 15:53:04 +01:00
6b5945add8 Added translations
ref #94
2021-03-26 15:34:38 +01:00
55693de934 Removed debug info
ref #94
2021-03-26 15:34:01 +01:00
d467475b6d Basic card generation worX 🎉🎉
ref #94
2021-03-26 15:32:27 +01:00
44bc14820f Fuggin snowpack bs
ref #94
2021-03-26 14:47:56 +01:00
008027db0e added windicss settings for VSCode
ref #108
2021-03-25 18:57:33 +01:00
aec5e3473e for await fix - ViteJS
ref #108
2021-03-25 18:56:18 +01:00
95c8fde72f updated default entrypoint
ref #108
2021-03-25 18:56:02 +01:00
0f32968fae 🐳 new Dockerfiles
ref #108
2021-03-25 18:55:43 +01:00
ae79e9fea1 basic ViteJS migration
ref #108
2021-03-25 18:55:24 +01:00
9aa8e7edff Merge pull request 'first merge to main 🚀' (#71) from dev into main
Reviewed-on: #71
Reviewed-by: Nicolai Ort <info@nicolai-ort.com>
2021-02-19 17:03:02 +00:00
71 changed files with 4938 additions and 2945 deletions

6
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM mcr.microsoft.com/vscode/devcontainers/base:alpine-3.12
RUN apk update
RUN apk add --upgrade nodejs-current npm
RUN npm i -g yarn rimraf
RUN rimraf node_modules
RUN yarn set version berry

View File

@@ -0,0 +1,20 @@
{
"name": "Node.js",
"build": {
"dockerfile": "Dockerfile"
},
"settings": {
"terminal.integrated.shell.linux": "/bin/sh"
},
"extensions": [
"dbaeumer.vscode-eslint",
"2gua.rainbow-brackets",
"christian-kohler.npm-intellisense",
"remimarsal.prettier-now",
"svelte.svelte-vscode",
"lokalise.i18n-ally",
"fivethree.vscode-svelte-snippets",
"voorjaar.windicss-intellisense"
],
"postCreateCommand": "yarn && yarn dev --open"
}

View File

@@ -1,3 +1 @@
public/env.sample.js public/env.sample.js
public/workbox-*.js
public/workbox-*.js.map

View File

@@ -27,7 +27,7 @@ name: build:dev
steps: steps:
- name: run full license export - name: run full license export
depends_on: ["clone"] depends_on: ["clone"]
image: node:alpine image: registry.odit.services/hub/library/node:alpine
commands: commands:
- yarn - yarn
- yarn licenses:export - yarn licenses:export

9
.gitignore vendored
View File

@@ -1,11 +1,10 @@
node_modules node_modules
build
package-lock.json package-lock.json
yarn.lock yarn.lock
*.map *.map
public/env.js public/env.js
public/sw.js
public/index.html
public/workbox-*.js
svelte.config.js
public/index.html public/index.html
/dist
.yarn
.pnp.js
.yarnrc.yml

View File

@@ -5,7 +5,8 @@
"remimarsal.prettier-now", "remimarsal.prettier-now",
"svelte.svelte-vscode", "svelte.svelte-vscode",
"lokalise.i18n-ally", "lokalise.i18n-ally",
"fivethree.vscode-svelte-snippets" "fivethree.vscode-svelte-snippets",
"voorjaar.windicss-intellisense"
], ],
"unwantedRecommendations": [ "unwantedRecommendations": [
"antfu.i18n-ally" "antfu.i18n-ally"

View File

@@ -1,4 +1,5 @@
{ {
"i18n-ally.localesPaths": "src/locales", "i18n-ally.localesPaths": "src/locales",
"i18n-ally.keystyle": "nested" "i18n-ally.keystyle": "nested",
"windicss.enableCodeFolding": false,
} }

View File

@@ -2,8 +2,311 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [0.15.3](https://git.odit.services/lfk/frontend/compare/0.15.2...0.15.3)
- Small bugfix (null got displayed) 🛠 [`224f586`](https://git.odit.services/lfk/frontend/commit/224f5863683ae2543a4a435510ed2c558dc5d307)
#### [0.15.2](https://git.odit.services/lfk/frontend/compare/0.15.1...0.15.2)
> 16 April 2021
- 🚀RELEASE v0.15.2 [`9add6c8`](https://git.odit.services/lfk/frontend/commit/9add6c8ff1fbeed91fe97a7cf262921b716f4e3c)
- Footer - noopener link [`cee04c1`](https://git.odit.services/lfk/frontend/commit/cee04c1d6fb6005cefe77fb95855ab6fe2cc448f)
- Hotfix: Team change recognition 🐞 [`cbec785`](https://git.odit.services/lfk/frontend/commit/cbec78589d2fa21f12ce87e71bff2b49c3a7d345)
- NGINX cache assets [`e54a480`](https://git.odit.services/lfk/frontend/commit/e54a4807f70bc333396885f81d3dcc7ae6c115d9)
#### [0.15.1](https://git.odit.services/lfk/frontend/compare/0.15.0...0.15.1)
> 16 April 2021
- 🚀RELEASE v0.15.1 [`a85db7c`](https://git.odit.services/lfk/frontend/commit/a85db7cb3f89881794e37a66ecd822f8ad5873f1)
- Merge pull request '🐞🐳 fix Dockerfile' (#138) from bugfix/136-opacity_reactivity into dev [`2bd3779`](https://git.odit.services/lfk/frontend/commit/2bd3779839de16a89b91a3da93033e2a2b742ab7)
- 🚚 move to tailwind [`07ac041`](https://git.odit.services/lfk/frontend/commit/07ac041d69b3b1810e5db538b53fe62084490f7a)
- 🐞🐳 fix Dockerfile [`303e33c`](https://git.odit.services/lfk/frontend/commit/303e33cafb4a1be01e4c4b43f46ff0c651cb4620)
- Dockerfile now uses selfhosted registry [`b4e689d`](https://git.odit.services/lfk/frontend/commit/b4e689dddf0b93a2794aa30ea83e8c6505d6bbfd)
- new license file version [CI SKIP] [`98a0b03`](https://git.odit.services/lfk/frontend/commit/98a0b036c5490b4bc4992e83f3bca02be39927fa)
- Merge pull request 'Opacity import fix bugfix/136-opacity_reactivity' (#137) from bugfix/136-opacity_reactivity into dev [`fb3f30f`](https://git.odit.services/lfk/frontend/commit/fb3f30fb1024de61ce1c541dae90374454f6ef96)
- Added bs import fix [`6213952`](https://git.odit.services/lfk/frontend/commit/621395200751c2d42b9ad44c77e84bda03b62e83)
#### [0.15.0](https://git.odit.services/lfk/frontend/compare/0.14.0...0.15.0)
> 15 April 2021
- 🚀RELEASE v0.15.0 [`5c02028`](https://git.odit.services/lfk/frontend/commit/5c02028841c68d9a284bf6971eec2b6bc2fdf1f3)
- Merge pull request 'Mark donations as payed feature/133-donation_payments' (#135) from feature/133-donation_payments into dev [`c561b53`](https://git.odit.services/lfk/frontend/commit/c561b536705a68215d9c0a6b320587d1647bf57f)
- Sorted translations [`c7a858e`](https://git.odit.services/lfk/frontend/commit/c7a858eed7962294bc9df3c92ce2e46b0a354796)
- Added total donation amount to donor overview [`e42ea94`](https://git.odit.services/lfk/frontend/commit/e42ea943b7821d433fe21599edbd9f76c3128ef2)
- Added Add Payment button to donor overview [`6e6e8b2`](https://git.odit.services/lfk/frontend/commit/6e6e8b26171f16542c101520800b4b6ea7c023d3)
- You can now open a modal to add a payment to a donation from the donation overview [`a943aaf`](https://git.odit.services/lfk/frontend/commit/a943aaf5fce8f113dd967d3842e2b0d7d50604e9)
- You can now add payments from the donation overview [`1dbab03`](https://git.odit.services/lfk/frontend/commit/1dbab03fe73b5e0fc011f9b0af7199bd71bc79c5)
- Added payment updating via detail [`bdcf5d3`](https://git.odit.services/lfk/frontend/commit/bdcf5d3fc08d250377226a253642d79b2e82d624)
- Added **all** missing toast translations [`de5aa92`](https://git.odit.services/lfk/frontend/commit/de5aa9237d261b5d47a8def35afa7f8e0089aea6)
- You can now mark fixed donations as already paid on creation [`3d3a10a`](https://git.odit.services/lfk/frontend/commit/3d3a10aafb16d371be9471eb5172f9251fb2583f)
- Added translations 🌎 [`d015f97`](https://git.odit.services/lfk/frontend/commit/d015f9739570c44a7a2fe6ba248c9a45c3047c62)
- Changed top info style for donation overview [`4c2c24a`](https://git.odit.services/lfk/frontend/commit/4c2c24af2ca5c2874a583b0fd93bee147a17f449)
- Added paid donation amount and status to donation detail [`5645eea`](https://git.odit.services/lfk/frontend/commit/5645eeaafaa4254edf1a81bc597ce0c7a9b03ff0)
- Added total donation amount to donation overview [`961477d`](https://git.odit.services/lfk/frontend/commit/961477d5224bc44b552d2fc2851d8514116f4e20)
- Fixed chante recognition bug for fixed donation [`0f0aae7`](https://git.odit.services/lfk/frontend/commit/0f0aae7ba4cf5dfab15d56ce48edbdbc7cb7e403)
- Added total donation amount to donor detail [`a5f7101`](https://git.odit.services/lfk/frontend/commit/a5f71015a6557d664e9d3f505613352792fc38cb)
- Added msiisng runner id conversion [`5761815`](https://git.odit.services/lfk/frontend/commit/57618156b49b2b0f0274f2126fef36a017d90022)
- AddDonationModal - vertical alignment for paid status [`18acac8`](https://git.odit.services/lfk/frontend/commit/18acac83bc6532e14d36b3399d867e026d0c88ac)
- Added missing updated comparison [`04a3038`](https://git.odit.services/lfk/frontend/commit/04a3038369f2717c43459318b7b5754ebbaa9e45)
- DonationsOverview contrast on action [`d7d4447`](https://git.odit.services/lfk/frontend/commit/d7d44470bb08ac06594bc400608c17eeacb0434b)
- Fixed typo [`4c0886a`](https://git.odit.services/lfk/frontend/commit/4c0886a5d9b91439967bc8f66b09a57177f967d0)
- Fixed styling [`865254d`](https://git.odit.services/lfk/frontend/commit/865254d646b5f7de15720551c67ae649601cbcd2)
- Changed top info style for donation detail [`000fc97`](https://git.odit.services/lfk/frontend/commit/000fc97beb14427f69d421ff2c96975dbbdc7a3a)
#### [0.14.0](https://git.odit.services/lfk/frontend/compare/0.13.1...0.14.0)
> 14 April 2021
- Merge pull request 'added donor receipt list download to DonorsOverview' (#134) from feature/132-export-donors-receipt-list into dev [`#132`](https://git.odit.services/lfk/frontend/issues/132)
- Sorted translations 🌎 [`c6c9751`](https://git.odit.services/lfk/frontend/commit/c6c97516b3981ef580d620c0c8a6fcc42f26facd)
- Fixed typos in translations [`03676b2`](https://git.odit.services/lfk/frontend/commit/03676b2894892c3559118b93e969c063b53b081e)
- added donor receipt list download to DonorsOverview [`d241ca5`](https://git.odit.services/lfk/frontend/commit/d241ca569838abbe9581fbd319f7f3b563cb7dcc)
- 🚀RELEASE v0.14.0 [`9c5fc6b`](https://git.odit.services/lfk/frontend/commit/9c5fc6b61c0bb2a6d831d4a23ef8679c6e68c6a1)
- ⏫ general version bump [`18f151c`](https://git.odit.services/lfk/frontend/commit/18f151c1fb878a74c3d1a2c2a2debf7913739417)
- new license file version [CI SKIP] [`302caf0`](https://git.odit.services/lfk/frontend/commit/302caf015f88f77e2b2ae2b67680e79f987ad81e)
- Switched to selfhosted images [`112eb29`](https://git.odit.services/lfk/frontend/commit/112eb29f932cd936f1d6c2308dcaeaf8cb642490)
- ⏫ bump @odit/lfk-client-js@0.11.0 [`9ca57fa`](https://git.odit.services/lfk/frontend/commit/9ca57fac2eeabbf25142a507fb9c0fa3c90b4e74)
- replace donationAmount with paidDonationAmount [`e90e56d`](https://git.odit.services/lfk/frontend/commit/e90e56d8b26aef23aba2bbb0c3942ba4d7feb224)
#### [0.13.1](https://git.odit.services/lfk/frontend/compare/0.13.0...0.13.1)
> 11 April 2021
- 🚀RELEASE v0.13.1 [`b512cf8`](https://git.odit.services/lfk/frontend/commit/b512cf86674f1c60b5ac790985ededdfd6554185)
- For await fix [`a24d292`](https://git.odit.services/lfk/frontend/commit/a24d2923c6e6da90d610c05183d29d47eaf2ed30)
#### [0.13.0](https://git.odit.services/lfk/frontend/compare/0.12.5...0.13.0)
> 11 April 2021
- 🚀RELEASE v0.13.0 [`467808a`](https://git.odit.services/lfk/frontend/commit/467808abefe127dac66a2837fcce3197dddb140f)
- Merge pull request 'Better org pdf generation feature/130-org_doc_splitting' (#131) from feature/130-org_doc_splitting into dev [`861f1f2`](https://git.odit.services/lfk/frontend/commit/861f1f221653283e7586aa2c67b205337fd44398)
- Org card generation now runs in sequence [`fef14b6`](https://git.odit.services/lfk/frontend/commit/fef14b6e4fb47ad92da61de91fedce96aea26b2c)
- Org certificate generation now runs in sequence [`509b22b`](https://git.odit.services/lfk/frontend/commit/509b22bea0dd3e4446e6ecc37d27644e9bf2ad50)
- Org contract generation now runs in sequence [`01d2a7e`](https://git.odit.services/lfk/frontend/commit/01d2a7e6aa709b3f2d71575f705fc962e97e2742)
- Emergency document server url change [`5476808`](https://git.odit.services/lfk/frontend/commit/5476808683a919bc34dbaea1f1ed276d49750096)
- Fixed const -&gt; let [`7447b2f`](https://git.odit.services/lfk/frontend/commit/7447b2f4c134a585905db6733093eab13e6f7c47)
- Hotfix: Org * generation🐞 [`ac586fe`](https://git.odit.services/lfk/frontend/commit/ac586fec5abd324d590ba99cdfe8ddddefbf95e6)
#### [0.12.5](https://git.odit.services/lfk/frontend/compare/0.12.4...0.12.5)
> 8 April 2021
- 🚀RELEASE v0.12.5 [`331d737`](https://git.odit.services/lfk/frontend/commit/331d737796c82454b1c19fa1840ccc20e36d2626)
- Merge pull request 'Added runner team's parentorg name to runenr overciew' (#129) from feature/128-runner_orgs into dev [`ef81b8a`](https://git.odit.services/lfk/frontend/commit/ef81b8adf9bef685a55936d7544bf645c0d6ecbe)
- Switched to html entity [`8a7d635`](https://git.odit.services/lfk/frontend/commit/8a7d635cef2d465e70c84e1f7a7b90b98a8dbab1)
- Added runner team's parentorg name to runenr overciew [`4c259c1`](https://git.odit.services/lfk/frontend/commit/4c259c1eef2b0166ce6a8493d0c9e9d5ede11146)
#### [0.12.4](https://git.odit.services/lfk/frontend/compare/0.12.3...0.12.4)
> 8 April 2021
- 🚀RELEASE v0.12.4 [`5b4ede5`](https://git.odit.services/lfk/frontend/commit/5b4ede5e2f6a26b475a7a4b430a4146d21fb9671)
- 🚑 [HOTFIX] - drop "svelte-infinite-loading" [`d0ab3dd`](https://git.odit.services/lfk/frontend/commit/d0ab3dda78bbad2cea18a2491056530897d56607)
#### [0.12.3](https://git.odit.services/lfk/frontend/compare/0.12.2...0.12.3)
> 8 April 2021
- Merge pull request 'fix' (#126) from bugfix/125-mobile into dev [`#125`](https://git.odit.services/lfk/frontend/issues/125)
- almost fixed... [`495a6b2`](https://git.odit.services/lfk/frontend/commit/495a6b22bd8036593f390bdb862d325524cefbcc)
- 🐞 bugfix for svelte x tailwind class names [`b6b07cf`](https://git.odit.services/lfk/frontend/commit/b6b07cf30cc6533bd5dbfec1f813c16fde85634d)
- fix [`0acaffb`](https://git.odit.services/lfk/frontend/commit/0acaffbdfa359e52654a5afe2788aa59fe6f9036)
- 🚀RELEASE v0.12.3 [`d9cf51b`](https://git.odit.services/lfk/frontend/commit/d9cf51b4bbc2136594a03c5d0eeb8cb3f3440b2a)
- custom css fix for collapsed_navigation [`ffbc243`](https://git.odit.services/lfk/frontend/commit/ffbc243194c7faeb4fe61c12711a1c441c3994ef)
- new license file version [CI SKIP] [`aa17f24`](https://git.odit.services/lfk/frontend/commit/aa17f242209f7e7cecff774ace7a35b581adec1f)
#### [0.12.2](https://git.odit.services/lfk/frontend/compare/0.12.1...0.12.2)
> 7 April 2021
- 🚀RELEASE v0.12.2 [`6043bc4`](https://git.odit.services/lfk/frontend/commit/6043bc45174d51ab110b0ed10a8679d96127ab87)
- Merge pull request 'feature/110-virtual_list' (#124) from feature/110-virtual_list into dev [`e6ed066`](https://git.odit.services/lfk/frontend/commit/e6ed066e3ffabba6519f94d801d21a27819d0492)
- wip on virtuallist [`6304116`](https://git.odit.services/lfk/frontend/commit/6304116edb7f5e3c7b67c15e0b1740d34c513155)
- fixed height table [`4cad86c`](https://git.odit.services/lfk/frontend/commit/4cad86cf852468428d77103d052c6974b17c34c3)
- pre-merge fixes [`37970d2`](https://git.odit.services/lfk/frontend/commit/37970d2be6b6502701914e41e5bfe2c418438480)
- updated virtual scroll list [`1376788`](https://git.odit.services/lfk/frontend/commit/1376788016e767f006661f8c9e6747781f2dce55)
#### [0.12.1](https://git.odit.services/lfk/frontend/compare/0.12.0...0.12.1)
> 6 April 2021
- 🚀RELEASE v0.12.1 [`834ff8f`](https://git.odit.services/lfk/frontend/commit/834ff8fa63178f36dcacf931c128ba67a3e7bd1b)
- Merge pull request 'ImportRunnerModal Cancel Button feature/122-import_cancel' (#123) from feature/112-import_cancel into dev [`1f428a5`](https://git.odit.services/lfk/frontend/commit/1f428a535e3ae619cbf8db51d04255aac8dd8614)
- Added cancel button for the first stage of runner import [`0c40966`](https://git.odit.services/lfk/frontend/commit/0c409669700d3a8096cc04716154b0fdca458fe5)
- Escape now triggers foll modal close (including reset) instead of just hiding th modal [`9da071f`](https://git.odit.services/lfk/frontend/commit/9da071fe9ba067160334682bf00163e3630fe919)
#### [0.12.0](https://git.odit.services/lfk/frontend/compare/0.11.0...0.12.0)
> 5 April 2021
- Merge pull request 'feature/108_vite_migration' (#118) from feature/108_vite_migration into dev [`#108`](https://git.odit.services/lfk/frontend/issues/108)
- 🚀RELEASE v0.12.0 [`892a04f`](https://git.odit.services/lfk/frontend/commit/892a04f28930481715eb486b1ef4efeb98a6e999)
- Fixed package version [`27cc972`](https://git.odit.services/lfk/frontend/commit/27cc9727f1d02d186c3ccadb06e5b4b1b1d6202d)
- Merge pull request 'Implmented certificate generation feature/119-Certificate_generation' (#120) from feature/119-Certificate_generation into dev [`f0738d4`](https://git.odit.services/lfk/frontend/commit/f0738d451b02e4a298b5f9cb8ab0be16aed10a38)
- The PFS Prefixes now get translated via i18n [`bfacfec`](https://git.odit.services/lfk/frontend/commit/bfacfec76511cae3015f52698fdcbd80a7a15981)
- Sorted translations 🌍 [`9e6a8da`](https://git.odit.services/lfk/frontend/commit/9e6a8daf2c394cf17da532382ec7d049a0f89577)
- added missing/ wrong translations + formatting! [`7c86a5e`](https://git.odit.services/lfk/frontend/commit/7c86a5eeb370a43451d180a09a501066b023b9a0)
- Added i18n [`17f6f4e`](https://git.odit.services/lfk/frontend/commit/17f6f4e616bf57424ee12ad53b939429c02a0171)
- Added basic certificate generation component [`af63ce6`](https://git.odit.services/lfk/frontend/commit/af63ce67ae7d8f8a70706c3bd6755197908996ff)
- basic ViteJS migration [`ae79e9f`](https://git.odit.services/lfk/frontend/commit/ae79e9fea1963e977ef468e8e56f87d68916fadd)
- Implemented generation for orgs [`2e3ac15`](https://git.odit.services/lfk/frontend/commit/2e3ac154be0bf0776cd00f7d510f41ec676ae690)
- Implemented generation for teams [`2472640`](https://git.odit.services/lfk/frontend/commit/2472640755e3e41259a44127a875d00517a25842)
- updated default entrypoint [`95c8fde`](https://git.odit.services/lfk/frontend/commit/95c8fde72fca5cd5a644d51a33dc88e0b59fce92)
- ⏫📍 version bump + pin [`b065b4f`](https://git.odit.services/lfk/frontend/commit/b065b4ff218d07952fa45989e6e2ee7df13e07c1)
- 🧹 reorder + fix package [`12433f7`](https://git.odit.services/lfk/frontend/commit/12433f7c236906fe2b29848a0acaa6be1724da56)
- 🔨 re-added VS Code devcontainer config [`9318709`](https://git.odit.services/lfk/frontend/commit/93187099d32c506329b1437642aae985f2850689)
- 🐳 new Dockerfiles [`0f32968`](https://git.odit.services/lfk/frontend/commit/0f32968fae8b55a13d387918211983d0e61f85ab)
- 📃 added readme [`aa24b1d`](https://git.odit.services/lfk/frontend/commit/aa24b1dce5d6d73c8f42fc57f81b692350bf9665)
- Copy-paste fix [`f47d5e3`](https://git.odit.services/lfk/frontend/commit/f47d5e347d97ee127fa0380620138a9672665cd5)
- 🔨🔥 alpine based devcontainer with working yarn PnP [`777304f`](https://git.odit.services/lfk/frontend/commit/777304f2593df36f4e89d2ba7680add183ff062f)
- Copy-paste fix [`7488a8b`](https://git.odit.services/lfk/frontend/commit/7488a8b597a148c309e1b4499d277fed7f3bf9f4)
- You can now generate certificates from the runner overview [`bb9b779`](https://git.odit.services/lfk/frontend/commit/bb9b779cee909ab85ef52f13be0a917f1c0a9e62)
- Cleaned up generation strings and added the schem for single runner generations for cards [`9c867e1`](https://git.odit.services/lfk/frontend/commit/9c867e106edd68784e6d19743519c1952a0f0bc7)
- Changed the basic nameing generation for runenr certificate files [`d65d379`](https://git.odit.services/lfk/frontend/commit/d65d3793de869bcd6733a1bbdac378d0bc1128b3)
- ⏫ version bumps [`d7fecfb`](https://git.odit.services/lfk/frontend/commit/d7fecfbd0bc01f1cd44dea3c3837e0cc44afab12)
- Cleaned up generation strings and added the schem for single runner generations for sponsoring contracts [`22b09d1`](https://git.odit.services/lfk/frontend/commit/22b09d16d0acc2883e3448dad95ed0f4ea7c6aeb)
- Certificate generation from org overview [`3177c6e`](https://git.odit.services/lfk/frontend/commit/3177c6eaa31636ed4546f4797775a0f0a930f5d1)
- certificate pdf names now include their locale [`304f28a`](https://git.odit.services/lfk/frontend/commit/304f28a3c10bc4745aa5b7c80d7ba0e651540706)
- Runnercard pdfs now include their locale [`3638d87`](https://git.odit.services/lfk/frontend/commit/3638d87bd2ff83618eefda5af18ba19e38e3c2eb)
- 🐞 fix await for esNext [`a922776`](https://git.odit.services/lfk/frontend/commit/a9227768de29305b51d10c8a6e4fa1d39b7d998f)
- Certificate generation from team overview [`18ec100`](https://git.odit.services/lfk/frontend/commit/18ec100c33a1fbab526187e769dbae54d9db0867)
- Added certificate generation from runner overview and detail [`7b685d6`](https://git.odit.services/lfk/frontend/commit/7b685d6cad97d2f7f48c4b19bfc128e1355b70c4)
- package cleanup [`6be2ee6`](https://git.odit.services/lfk/frontend/commit/6be2ee626addaf5113b4b4821bd99a276bf4f329)
- sponsoring pdf names now include their locale [`0bae5bf`](https://git.odit.services/lfk/frontend/commit/0bae5bf32b8687057dca50cde21022ea8c3abee8)
- ✨ update licenses.json [`e99e9e0`](https://git.odit.services/lfk/frontend/commit/e99e9e07089520d5a48021e10d2af7739656641a)
- added windicss settings for VSCode [`008027d`](https://git.odit.services/lfk/frontend/commit/008027db0e2736a9bb9defd67178ab3fe580de04)
- Certificate generation from team detail [`acd2f05`](https://git.odit.services/lfk/frontend/commit/acd2f0519d62e55dad8e9c3c41e77b6967212502)
- ⚡💾 prevent env.js from being cached [`c5d1553`](https://git.odit.services/lfk/frontend/commit/c5d155396a92dfee6d592fb24a936ab521215f6d)
- for await fix - ViteJS [`aec5e34`](https://git.odit.services/lfk/frontend/commit/aec5e3473e687415fbfd69c550d9b012e1b1be43)
- Certificate generation from org detail [`e6ffc37`](https://git.odit.services/lfk/frontend/commit/e6ffc371e1ca2d4451e7dd4a3ca3c05564edb5fb)
- 🧹 drop unused dependencies [`ce50fa2`](https://git.odit.services/lfk/frontend/commit/ce50fa2a62f8ff98e8be9c66432caeebb3952019)
- 🐞 fix NGINX config [`5352410`](https://git.odit.services/lfk/frontend/commit/5352410d0c76fd14575d7beafc6a83f028062efe)
- Fixed wrong permissiong getting checked [`b97a928`](https://git.odit.services/lfk/frontend/commit/b97a92860d71eb0384170e245a67fa3ea3fd8e85)
- new license file version [CI SKIP] [`5cc4871`](https://git.odit.services/lfk/frontend/commit/5cc4871ec4be9f0af07738f6e3d44bdbe31cd25a)
- ⏫ bump @odit/lfk-client-js@0.10.1 [`8b74d6d`](https://git.odit.services/lfk/frontend/commit/8b74d6d759c8481f012c201e2ea0d12b29ddef90)
- 🔨 dev container open ⚡ [`ceb2146`](https://git.odit.services/lfk/frontend/commit/ceb2146c1b08bbe9250e4db7846e06bd89526c21)
- ⏫ version bump: vite-plugin-windicss@0.12.2 [`8d006d8`](https://git.odit.services/lfk/frontend/commit/8d006d8c74d71c43a9031d58f5a8c7fc55ed95fc)
- 🚚 move @svitejs/vite-plugin-svelte to @sveltejs/vite-plugin-svelte [`44b53da`](https://git.odit.services/lfk/frontend/commit/44b53da34516b00671b3e5060ba831e409ac3dd5)
- ⏫ upgrade vite-plugin-windicss@0.12.1 [`ab45fc1`](https://git.odit.services/lfk/frontend/commit/ab45fc144eaf14f63d86ee53c1db4eefd88f9c7f)
- 🐞 fix main.js linking [`467404b`](https://git.odit.services/lfk/frontend/commit/467404bfc87f3c08c5e227f194d71eea7cc48921)
- 🐞 fix vite config for production system [`10a011d`](https://git.odit.services/lfk/frontend/commit/10a011d8426e475105ff5d2d5cf4adca2ef7625c)
- fix dev script [`eb3ede9`](https://git.odit.services/lfk/frontend/commit/eb3ede9593e2e527df3e3a2f81c8e179bb555f51)
- ⏫ bump vite to 2.1.3 [`0cd3e93`](https://git.odit.services/lfk/frontend/commit/0cd3e937d852eeabe43eb6298bfabe20834240b2)
- Removed useless console.log [`d23dbaa`](https://git.odit.services/lfk/frontend/commit/d23dbaaf695a60fe5ebbc9945646a16b5fc45a16)
- Removed useless console log [`48cfc15`](https://git.odit.services/lfk/frontend/commit/48cfc15cfb09096db1bd5ddbe9138b1a604d581f)
#### [0.11.0](https://git.odit.services/lfk/frontend/compare/0.10.0...0.11.0)
> 30 March 2021
- Added button (including translations [`0614c76`](https://git.odit.services/lfk/frontend/commit/0614c76e924b18b512bab59933a26fec07cf483d)
- Added button (including translations [`97e338f`](https://git.odit.services/lfk/frontend/commit/97e338f9d4f388596d550990457254c7fa1a3492)
- Sorted translations [`89bb9c2`](https://git.odit.services/lfk/frontend/commit/89bb9c215e356e0940678f5cabd9e38bc203040e)
- Added function for generating cards with pdf [`c8d6390`](https://git.odit.services/lfk/frontend/commit/c8d639024a5f2f72d6e30d2ce990b08bd71a5471)
- 🚀RELEASE v0.11.0 [`f8ccf4f`](https://git.odit.services/lfk/frontend/commit/f8ccf4f5d8f68ecee31430029889b8ab1ecec682)
- Fixed button styling [`08cb079`](https://git.odit.services/lfk/frontend/commit/08cb079e9798392e26515d559af2637e74deea97)
- Now returning cards on creation with pdf [`1d999d4`](https://git.odit.services/lfk/frontend/commit/1d999d4910acb5efa21b3f9922cdb359babff404)
- new license file version [CI SKIP] [`8f8b998`](https://git.odit.services/lfk/frontend/commit/8f8b9988ad94ee9f3729a3fe6fdb4c558828d892)
- Merge pull request 'Generate and print bulk blank cards feature/116-download_blanc_cards' (#117) from feature/116-download_blanc_cards into dev [`25d8b86`](https://git.odit.services/lfk/frontend/commit/25d8b86efd89c442d1bf308a8743134820acfd1f)
- Added comment [`636f018`](https://git.odit.services/lfk/frontend/commit/636f018daa33b99468a257bfc33477e1e644d081)
- Bumped lfk client js version [`2d18686`](https://git.odit.services/lfk/frontend/commit/2d18686ce782a434ca7bd34c07c36a35b9497273)
- Bumped lfk-client-js [`7dfaa75`](https://git.odit.services/lfk/frontend/commit/7dfaa7579a22b13194fcdd1c02b4437958261472)
#### [0.10.0](https://git.odit.services/lfk/frontend/compare/0.9.1...0.10.0)
> 26 March 2021
- Added translations [`79c447b`](https://git.odit.services/lfk/frontend/commit/79c447b4c65e55ebb5af71fb0b09174c36e2cecf)
- Sorted translations 🌍 [`5bd3a46`](https://git.odit.services/lfk/frontend/commit/5bd3a463f00abaf2c98ab554f88e5542d01f364a)
- Reset can now only be triggered if pw is strong enoug [`75d8f73`](https://git.odit.services/lfk/frontend/commit/75d8f7331b6ae78f3979bb62148188a16f83cb8d)
- Module now exports functions that check if a password is strong enough and equal to a potential confirmation field [`b2509e9`](https://git.odit.services/lfk/frontend/commit/b2509e9e53ab6b51dfd55e26712e8928160cd64b)
- 🚀RELEASE v0.10.0 [`f7fc196`](https://git.odit.services/lfk/frontend/commit/f7fc1967a50f302af1d8b668628be2f4ab2975a3)
- Added more cirteria to the password strength component [`5fa9939`](https://git.odit.services/lfk/frontend/commit/5fa9939696a35d60d762feb0cebef61d31869218)
- Now using pw strength component [`6aaf838`](https://git.odit.services/lfk/frontend/commit/6aaf8384512185a3a319ce6b3e2505e910468e64)
- Added a password strength verification [`ad3bd31`](https://git.odit.services/lfk/frontend/commit/ad3bd312e9a5785f81029ea2b7e302ea1addd988)
- Implemented a custom password strength component [`4956bb0`](https://git.odit.services/lfk/frontend/commit/4956bb0e9c3c1d22d60e849aea5664e35330f897)
- User creation can now only be triggered if pw is strong enoug [`540304f`](https://git.odit.services/lfk/frontend/commit/540304f947f60a7072c592ca8088996ce7e95cb4)
- Now using pw strength component for user creation [`7862f44`](https://git.odit.services/lfk/frontend/commit/7862f446532903f1a2eac7b21d5c80c3245785e5)
- Added missing exports [`962dd0c`](https://git.odit.services/lfk/frontend/commit/962dd0c1bbc0df7f20bcec5b4247188c8935c87e)
- new license file version [CI SKIP] [`aedb8a7`](https://git.odit.services/lfk/frontend/commit/aedb8a765ba053545adbba9eb014b3bb0e5aac8c)
- Bumped lfk-client version 🔝 [`cf58bd1`](https://git.odit.services/lfk/frontend/commit/cf58bd15c3541c417ab2be83d96135e931a2b6f6)
- new license file version [CI SKIP] [`34f4f68`](https://git.odit.services/lfk/frontend/commit/34f4f68524918fd3d1963966a1e259d5b60efaca)
- Merge pull request 'Implemented password strength test feature/106-password_strength' (#115) from feature/106-password_strength into dev [`09b8144`](https://git.odit.services/lfk/frontend/commit/09b81440804cf98303fcb723a9717d6d0f432da8)
- Formatting🛠 [`4167819`](https://git.odit.services/lfk/frontend/commit/4167819e7a864d3b1dd95ba48ab1525a454f7f30)
- Now using pw strength component for reset [`5d5f7c7`](https://git.odit.services/lfk/frontend/commit/5d5f7c7f5c6a69146f41996f4facfeff95791be0)
#### [0.9.1](https://git.odit.services/lfk/frontend/compare/0.9.0...0.9.1)
> 26 March 2021
- 🚀RELEASE v0.9.1 [`2ca63fd`](https://git.odit.services/lfk/frontend/commit/2ca63fd1f675f0da2b18ba43095074dd4823991d)
- Merge pull request 'Org selfservice Link feature/112-org_registration_links' (#114) from feature/112-org_registration_links into dev [`a5d25e7`](https://git.odit.services/lfk/frontend/commit/a5d25e7d92c7c37e90dbb4ba74b787873f920c6b)
- Added checkbox to enable registration [`f9fe793`](https://git.odit.services/lfk/frontend/commit/f9fe79357317653b46c09eb95b0db13845cddcf9)
- Sorted translations [`c074c12`](https://git.odit.services/lfk/frontend/commit/c074c12be75f285612f7a732c106404d9fb4538a)
- You can now copy the selfservice links to your clipboard [`fcd55f8`](https://git.odit.services/lfk/frontend/commit/fcd55f89d72e6ceb9bb2bdd194cc3420145d6d0d)
- Formatting [`f185d55`](https://git.odit.services/lfk/frontend/commit/f185d559c0d6476f2f2b9ea74aaad3297411801d)
- Copy now 100% worX [`a3921b4`](https://git.odit.services/lfk/frontend/commit/a3921b45c70b410293db593a75d2fdd34c131733)
- Fixed changes in wrong file [`73d95bc`](https://git.odit.services/lfk/frontend/commit/73d95bc0042f8f586ba2f2345342e25da1d280c2)
- Added check if key exists [`c2d29ff`](https://git.odit.services/lfk/frontend/commit/c2d29ff233f6b3e9dd2555b7e0b845592da2ba35)
- Added check if key exists [`2316baa`](https://git.odit.services/lfk/frontend/commit/2316baa8984832382be9f3b4549ca62cf9ccb5a3)
- Added translations [`ddbc293`](https://git.odit.services/lfk/frontend/commit/ddbc293e9ca0525910bf3d995de970ee2c35c56a)
- new license file version [CI SKIP] [`ded9b58`](https://git.odit.services/lfk/frontend/commit/ded9b589fe087915176c5b54f3c55e412541bc8f)
- Merge pull request 'first merge to main 🚀' (#71) from dev into main [`9aa8e7e`](https://git.odit.services/lfk/frontend/commit/9aa8e7edffa7e51b00a5ab7a8f16828b7a469181)
#### [0.9.0](https://git.odit.services/lfk/frontend/compare/0.8.7...0.9.0)
> 26 March 2021
- 🚀RELEASE v0.9.0 [`67c3732`](https://git.odit.services/lfk/frontend/commit/67c3732fad5a7c64ae11dcbebaaa095e1a2b387c)
- Merge pull request 'Runner cards feature/94-runnercard_mgnt' (#111) from feature/94-runnercard_mgnt into dev [`2932f45`](https://git.odit.services/lfk/frontend/commit/2932f4591e62187a4903511051d110e9679c0993)
- Sorted translations 🌍 [`1665a1a`](https://git.odit.services/lfk/frontend/commit/1665a1a093862a13be78ec65dcdf64eb7d855593)
- Added translations [`6b5945a`](https://git.odit.services/lfk/frontend/commit/6b5945add86a77630c500872545bb91724b2047f)
- Sorted translations 🌍 [`d6c315a`](https://git.odit.services/lfk/frontend/commit/d6c315ab8e020bc65b967e2c3f4cd921392d66d5)
- Sorted translations [`de2fe0e`](https://git.odit.services/lfk/frontend/commit/de2fe0e9f171efb3deeea8cfe638f60e3ca90423)
- Added basic cards page [`5c5ef95`](https://git.odit.services/lfk/frontend/commit/5c5ef95d2be65c0e951dcd472113c8ce0593c9e0)
- Moved contract generation to it's own component [`0cfc87f`](https://git.odit.services/lfk/frontend/commit/0cfc87fbe6adfacab5c2fab732866aead3231fbf)
- Teams now use the new sponsoring contracts module [`014ba3b`](https://git.odit.services/lfk/frontend/commit/014ba3bf6718ff28f35c67c8f732b53aae50723c)
- Basic card generation worX 🎉🎉 [`d467475`](https://git.odit.services/lfk/frontend/commit/d467475b6d61d50bec3a043ea8792533e8593df6)
- Orgs now use the new sponsoring contracts module [`8b451b3`](https://git.odit.services/lfk/frontend/commit/8b451b3c6794e7df09898a687533ce8fadd56192)
- Added runnercard detail/edit modal [`0313f8c`](https://git.odit.services/lfk/frontend/commit/0313f8cc495088df1237d00e6b9ed1a94f019644)
- Implemented Add card modal [`535b23a`](https://git.odit.services/lfk/frontend/commit/535b23ae917de154e08962f5d486c50d6e091fe0)
- Added bulk card creation modal [`8a32569`](https://git.odit.services/lfk/frontend/commit/8a32569a3be1ad26ba163f4e2b67a368cfeeb422)
- Added basic card overview [`c6a1526`](https://git.odit.services/lfk/frontend/commit/c6a15264b3d13d516f3d97ea4b891ed1c328cead)
- Fixed org generation not hiding the generation toast [`c87321f`](https://git.odit.services/lfk/frontend/commit/c87321f804858f84fcccd85a15b9c3fb003c18be)
- Working runner runnercard generation [`c907486`](https://git.odit.services/lfk/frontend/commit/c907486c4d1c64114124deb3cd0d0cf11d38a6b1)
- Implemented bulk creation [`7ad6b73`](https://git.odit.services/lfk/frontend/commit/7ad6b73574174f24f2d6f23b3caf4823881a85e7)
- Now w/ working dialog🎉🎉🎉 [`ae96730`](https://git.odit.services/lfk/frontend/commit/ae9673070c3959ff6190a37123f3fc609b182c5a)
- Now w/working editing [`fac059f`](https://git.odit.services/lfk/frontend/commit/fac059f02cae84261443ee95448ec8db06dd755a)
- Added runnercard generation for teams [`23e0b53`](https://git.odit.services/lfk/frontend/commit/23e0b53107623c505d07a99a51ce836c9324acce)
- Added bulk creation modal to cards view [`f46ccb6`](https://git.odit.services/lfk/frontend/commit/f46ccb610e01654a4ee5e47d78ab500045dd494b)
- Added a new runenrcard logo [`acf78a8`](https://git.odit.services/lfk/frontend/commit/acf78a88221d0988f6501ae341e028a4113b578d)
- Moved modal import to overview for simplification [`1a52aaf`](https://git.odit.services/lfk/frontend/commit/1a52aaf8d1ad19b03d355aec0e1c48182db024f9)
- Added CardsEmptyState + Emtystate graphic [`2d0beaa`](https://git.odit.services/lfk/frontend/commit/2d0beaaaad4efefd036bbef09f10c8c22bdb2760)
- Added message for missing runner/blanco card) [`4715978`](https://git.odit.services/lfk/frontend/commit/4715978f810bbb283876f06d147b1ec86d373786)
- Fixed counting bug [`f5c1ec9`](https://git.odit.services/lfk/frontend/commit/f5c1ec9939d856804c9ec3ead4b3ed869fc2ea63)
- Added card generation/printing from detail [`4a36fb6`](https://git.odit.services/lfk/frontend/commit/4a36fb6d952d9fe4d5edbe1ed0779c7fbcd50ef0)
- Formatting [`a516aa7`](https://git.odit.services/lfk/frontend/commit/a516aa7775faa2244862bb2e3c4de623c6405e5b)
- Moved the pdf generation related componenets to their own folder [`ddd82a7`](https://git.odit.services/lfk/frontend/commit/ddd82a71a7b67ead892626addfd56ba4cc632750)
- Now routing the cards page [`e852305`](https://git.odit.services/lfk/frontend/commit/e852305400a139f8169350077c30012aed556828)
- Fuggin snowpack bs [`44bc148`](https://git.odit.services/lfk/frontend/commit/44bc14820fed26d5e0d8b12ecd6b46ca0608ae7b)
- Formatting [`9f7d223`](https://git.odit.services/lfk/frontend/commit/9f7d2234fb9603a7391ec9a64253724c2c25c333)
- Now with working org runenrcard generation [`4b3d38b`](https://git.odit.services/lfk/frontend/commit/4b3d38b05b3ed74fc3c0d77e00fa2ed245e6325c)
- Now importing runner overview [`77e9c20`](https://git.odit.services/lfk/frontend/commit/77e9c205f94cf56c2e3584444899adb1e8bdf3c6)
- CardsOverview - move to 'enabled' language key [`df53c07`](https://git.odit.services/lfk/frontend/commit/df53c0745035a220d4c07fdce1b5a5e4d763411d)
- ✒ typo - "Geb" -&gt; "Gebe" [`f794af0`](https://git.odit.services/lfk/frontend/commit/f794af0950de59a7a7b468c30abdcb5c145f65fb)
- Bumped lfk client lib version [`3cd0468`](https://git.odit.services/lfk/frontend/commit/3cd0468b1921824b131178cb02677540b079f9b0)
- drop console log - CardDetailModal [`40899e9`](https://git.odit.services/lfk/frontend/commit/40899e9d80ba07a3fbbcac72782db53d98dc318e)
- Removed debug info [`55693de`](https://git.odit.services/lfk/frontend/commit/55693de93420c2d76af296fcacc6bcad644a3cbf)
#### [0.8.7](https://git.odit.services/lfk/frontend/compare/0.8.6...0.8.7) #### [0.8.7](https://git.odit.services/lfk/frontend/compare/0.8.6...0.8.7)
> 25 March 2021
- 🚀RELEASE v0.8.7 [`0af2647`](https://git.odit.services/lfk/frontend/commit/0af26479656393b0baea88f6f83c778740a67e62)
- Fixed listen on wrong permission🐞 [`0844215`](https://git.odit.services/lfk/frontend/commit/08442154f4bf94fc1101808b4585dc1f95afe8b2) - Fixed listen on wrong permission🐞 [`0844215`](https://git.odit.services/lfk/frontend/commit/08442154f4bf94fc1101808b4585dc1f95afe8b2)
#### [0.8.6](https://git.odit.services/lfk/frontend/compare/0.8.5...0.8.6) #### [0.8.6](https://git.odit.services/lfk/frontend/compare/0.8.5...0.8.6)

View File

@@ -1,18 +1,12 @@
FROM node:15.5.1-alpine3.12 FROM registry.odit.services/hub/library/node:15.14.0-alpine3.13
WORKDIR /app WORKDIR /app
RUN npm i -g pnpm
COPY package.json ./ COPY package.json ./
RUN pnpm i RUN yarn
COPY package.json *.config.js workbox-config.js template-copy.js index.template.html s-config.template.js ./ COPY package.json *.config.js postcss.config.cjs index.html ./
COPY src ./src COPY src ./src
COPY public ./public COPY public ./public
RUN pnpm run build RUN yarn build
# final image # final image
FROM alpine FROM registry.odit.services/hub/fholzer/nginx-brotli:v1.19.1
COPY --from=0 /app/build /app COPY --from=0 /app/dist /usr/share/nginx/html
RUN rm -rf /app/build/_dist_/components
RUN rm -rf /app/build/_dist_/locales
RUN rm -rf /app/build-manifest.json
FROM fholzer/nginx-brotli:v1.19.1
COPY --from=1 /app /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf COPY ./nginx.conf /etc/nginx/nginx.conf

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
# @odit/lfk-frontend
## ✒️ Overview
This is an API client for [https://git.odit.services/lfk/backend](@lfk/backend)
- WebApp built with [Svelte](https://svelte.dev), [WindiCSS](https://windicss.org/) (to compile [TailwindCSS](https://tailwindcss.com/)) and [Vite](https://vitejs.dev).
This application is intended for use by admin users/ members only.
## 🚀 Getting Started
```
yarn
```
## Development
```
yarn dev
/
yarn dev --open
```
## Build
```
yarn build
```

View File

@@ -10,14 +10,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Lauf Für Kaya! - Admin" /> <meta name="description" content="Lauf Für Kaya! - Admin" />
<title>Lauf für Kaya! - Admin</title> <title>Lauf für Kaya! - Admin</title>
__TAILWIND_INSERT__
</head> </head>
<body> <body>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.8.7-RELEASE_INFO</span> <span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.15.3-RELEASE_INFO</span>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script> <script src="/env.js"></script>
<script defer type="module" src="/_dist_/index.js"></script> <script type="module" src="/src/main.js"></script>
</body> </body>
</html> </html>

View File

@@ -6,6 +6,20 @@ http {
server { server {
error_page 404 /index.html; error_page 404 /index.html;
root /usr/share/nginx/html; root /usr/share/nginx/html;
location /assets {
expires 1y;
log_not_found off;
access_log off;
}
location = /index.html {
add_header Cache-Control 'no-store';
}
location = / {
add_header Cache-Control 'no-store';
}
location = /env.js {
add_header Cache-Control 'no-store';
}
location / { location / {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }

View File

@@ -1,45 +1,40 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "0.8.7", "version": "0.15.3",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",
"dev:all": "yarn prebuild && snowpack dev", "dev": "vite",
"dev": "cross-env NODE_ENV_ODIT=development_fast node template-copy.js && yarn build:sw && snowpack dev", "build": "vite build",
"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", "release": "release-it",
"licenses:export": "license-exporter --json -o public" "licenses:export": "license-exporter --json -o public"
}, },
"license": "CC-BY-NC-SA-4.0", "license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@odit/lfk-client-js": "0.7.0",
"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": { "devDependencies": {
"@odit/license-exporter": "^0.0.11", "@odit/lfk-client-js": "0.11.0",
"@snowpack/plugin-svelte": "3.5.2", "@odit/license-exporter": "0.0.11",
"auto-changelog": "^2.2.1", "@sveltejs/vite-plugin-svelte": "1.0.0-next.6",
"autoprefixer": "10.2.5", "@types/html-minifier": "4.0.0",
"cross-env": "^7.0.3", "auto-changelog": "2.2.1",
"postcss": "8.2.8", "autoprefixer": "^10.2.5",
"postcss-load-config": "3.0.1", "check-password-strength": "2.0.2",
"release-it": "^14.4.1", "csvtojson": "2.0.10",
"snowpack": "3.0.13", "gridjs": "3.4.0",
"svelte": "3.35.0", "html-minifier": "4.0.0",
"svelte-preprocess": "4.6.9", "localforage": "1.9.0",
"workbox-cli": "6.1.2" "marked": "2.0.3",
"postcss": "^8.2.10",
"release-it": "14.6.1",
"svelte": "3.37.0",
"svelte-focus-trap": "1.2.0",
"svelte-i18n": "3.3.9",
"svelte-preprocess": "^4.7.0",
"svelte-select": "3.17.0",
"tailwindcss": "^2.1.1",
"tinro": "0.6.1",
"toastify-js": "1.10.0",
"validator": "13.5.2",
"vite": "2.1.5",
"xlsx": "0.16.9"
}, },
"release-it": { "release-it": {
"git": { "git": {
@@ -55,7 +50,7 @@
"publish": false "publish": false
}, },
"hooks": { "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" "after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js && git add index.html && node order.js && git add src/locales"
} }
} }
} }

6
postcss.config.cjs Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

View File

@@ -1,5 +1,6 @@
const config = { const config = {
baseurl: 'http://localhost:4010', baseurl: 'http://localhost:4010',
baseurl_documentserver: 'http://localhost:4010/documents',
documentserver_key: 'NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe', documentserver_key: 'NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe',
// optional // optional
default_username: 'demo', default_username: 'demo',

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
const sveltePreprocess = require('svelte-preprocess');
const preprocess = sveltePreprocess(__insert__);
module.exports = {
preprocess
};

View File

@@ -1,26 +0,0 @@
/** @type {import("snowpack").SnowpackUserConfig } */
module.exports = {
mount: {
public: '/',
src: '/_dist_'
},
plugins: [ '@snowpack/plugin-svelte' ],
routes: [
/* Enable an SPA Fallback in development: */
{ match: 'routes', src: '.*', dest: '/index.html' }
],
packageOptions: {
/* ... */
sourceMap: false
},
devOptions: {
/* ... */
},
buildOptions: {
/* ... */
},
alias: {
/* ... */
},
optimize: { bundle: true, minify: true }
};

View File

@@ -1,5 +1,4 @@
<script> <script>
import "./TailwindStyles.svelte";
import "toastify-js/src/toastify.css"; import "toastify-js/src/toastify.css";
import "gridjs/dist/theme/mermaid.css"; import "gridjs/dist/theme/mermaid.css";
import { Route, router } from "tinro"; import { Route, router } from "tinro";
@@ -53,7 +52,6 @@
import { OpenAPI } from "@odit/lfk-client-js"; import { OpenAPI } from "@odit/lfk-client-js";
import UserDetail from "./components/users/UserDetail.svelte"; import UserDetail from "./components/users/UserDetail.svelte";
OpenAPI.BASE = config.baseurl; OpenAPI.BASE = config.baseurl;
import { register as registerSW } from "./swmodule";
import TeamDetail from "./components/teams/TeamDetail.svelte"; import TeamDetail from "./components/teams/TeamDetail.svelte";
import UserPermissions from "./components/users/UserPermissions.svelte"; import UserPermissions from "./components/users/UserPermissions.svelte";
import GroupPermissions from "./components/groups/GroupPermissions.svelte"; import GroupPermissions from "./components/groups/GroupPermissions.svelte";
@@ -75,7 +73,6 @@
import ScanDetail from "./components/scans/ScanDetail.svelte"; import ScanDetail from "./components/scans/ScanDetail.svelte";
import Cards from "./components/cards/Cards.svelte"; import Cards from "./components/cards/Cards.svelte";
store.init(); store.init();
registerSW();
</script> </script>
<Route> <Route>

View File

@@ -1,6 +0,0 @@
<style global>
/*! @import */
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>

View File

@@ -76,7 +76,7 @@
// last login was not processed yet // last login was not processed yet
} else { } else {
Toastify({ Toastify({
text: "chill...", text: $_('please-wait-a-moment-your-login-is-still-being-processed'),
duration: 1500, duration: 1500,
backgroundColor: backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",

View File

@@ -0,0 +1,52 @@
<script context="module">
import { passwordStrength } from "check-password-strength";
export function password_strong_enough(password_change) {
let strength = passwordStrength(password_change);
return (
strength?.contains.includes("lowercase") &&
strength?.contains.includes("uppercase") &&
strength?.contains.includes("number") &&
strength?.length > 9
);
}
export function password_strong_enough_and_equal(
password_change,
password_confirm
) {
return (
password_strong_enough(password_change) &&
password_change === password_confirm
);
}
</script>
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { passwordStrength as Strength } from "check-password-strength";
export let password_change;
export let password_confirm;
$: strength = Strength(password_change);
$: passwords_match =
!password_confirm || password_confirm === password_change;
</script>
<div class="ml-4">
<ul class="list-disc font-medium tracking-wide text-red-500 text-xs">
{#if !strength.contains.includes('lowercase')}
<li>{$_('must-contain-a-lowercase-letter')}</li>
{/if}
{#if !strength.contains.includes('uppercase')}
<li>{$_('must-contain-a-uppercase-letter')}</li>
{/if}
{#if !strength.contains.includes('number')}
<li>{$_('must-contain-a-number')}</li>
{/if}
{#if !(strength.length > 9)}
<li>{$_('must-be-at-least-10-characters-long')}</li>
{/if}
{#if !(passwords_match == true)}
<li>{$_('passwords-dont-match')}</li>
{/if}
</ul>
</div>

View File

@@ -1,38 +1,43 @@
<script> <script>
import { AuthService } from "@odit/lfk-client-js"; import { AuthService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import "toastify-js/src/toastify.css"; import "toastify-js/src/toastify.css";
import PasswordStrength, {
password_strong_enough,
} from "../auth/PasswordStrength.svelte";
let state = "reset_in_progress"; let state = "reset_in_progress";
let password = ""; let password = "";
export let params; export let params;
function set_new_password() { function set_new_password() {
if(password.trim() !== ""){ if (password.trim() !== "") {
Toastify({ Toastify({
text: $_('password-reset-in-progress'), text: $_("password-reset-in-progress"),
duration: 3500, duration: 3500,
}).showToast(); }).showToast();
AuthService.authControllerResetPassword(atob(params.resetkey),{ password }) AuthService.authControllerResetPassword(atob(params.resetkey), {
password,
})
.then((resp) => { .then((resp) => {
Toastify({ Toastify({
text: $_('password-reset-successful'), text: $_("password-reset-successful"),
duration: 3500, duration: 3500,
}).showToast(); }).showToast();
state="reset_success"; state = "reset_success";
}) })
.catch((err) => { .catch((err) => {
state="reset_error"; state = "reset_error";
}); });
} else { } else {
Toastify({ Toastify({
text: $_('please-provide-a-password'), text: $_("please-provide-a-password"),
duration: 3500, duration: 3500,
}).showToast(); }).showToast();
} }
} }
</script> </script>
{#if state==="reset_success"} {#if state === 'reset_success'}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
@@ -56,31 +61,31 @@
</div> </div>
</div> </div>
</div> </div>
{:else if state==="reset_error"} {:else if state === 'reset_error'}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900"> <p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')} {$_('application_name')}
</p> </p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold"> <p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold">
{$_('password-reset-failed')} {$_('password-reset-failed')}
</p> </p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900"> <p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_('please-request-a-new-reset-mail')} {$_('please-request-a-new-reset-mail')}
</p> </p>
<div class="mt-6">
<div class="mt-6"> <div class="mt-6">
<a <div class="mt-6">
href="/forgot_password/" <a
class="text-center relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"> href="/forgot_password/"
{$_('request-a-new-reset-mail')} class="text-center relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
</a> {$_('request-a-new-reset-mail')}
</a>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> {:else if state === 'reset_in_progress'}
{:else if state==="reset_in_progress"}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
@@ -102,11 +107,14 @@
placeholder={$_('new-password')} placeholder={$_('new-password')}
bind:value={password} /> bind:value={password} />
</div> </div>
<PasswordStrength bind:password_change={password} />
</div> </div>
<div class="mt-5"> <div class="mt-5">
<button <button
on:click={set_new_password} on:click={set_new_password}
disabled={!password_strong_enough(password)}
class:opacity-50={!password_strong_enough(password)}
type="submit" type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"> class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
<span class="absolute left-0 inset-y pl-3"> <span class="absolute left-0 inset-y pl-3">

View File

@@ -0,0 +1,6 @@
<!--
Temporary tailwind import fixes for classes that wouldn't be directly used otherwise.
Or as others may call it: Real big bullshit time.
Issue: https://git.odit.services/lfk/frontend/issues/136
-->
<div class="opacity-50"></div>

View File

@@ -1,158 +1,240 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap"; import { focusTrap } from "svelte-focus-trap";
import { RunnerCardService } from "@odit/lfk-client-js"; import { RunnerCardService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let bulk_modal_open; export let bulk_modal_open;
export let current_cards; export let current_cards;
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
$: card_count = 0; $: card_count = 0;
$: is_card_count_valid = card_count > 0; $: is_card_count_valid = card_count > 0;
$: processed_last_submit = true; $: processed_last_submit = true;
$: createbtnenabled = is_card_count_valid; $: createbtnenabled = is_card_count_valid;
(() => { (() => {
document.onkeydown = (e) => { document.onkeydown = (e) => {
e = e || window.event; e = e || window.event;
if (e.key === "Escape") { if (e.key === "Escape") {
bulk_modal_open = false; bulk_modal_open = false;
} }
if (e.keyCode === 13) { if (e.keyCode === 13) {
if (createbtnenabled === true) { if (createbtnenabled === true) {
createbtnenabled = false; createbtnenabled = false;
submit(); submit_with_print();
} }
} }
}; };
})(); })();
function submit() { function submit_without_print() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: $_("creating-blanco-cards"), text: $_("creating-blanco-cards"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count) RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, false)
.then((result) => { .then((result) => {
bulk_modal_open = false; bulk_modal_open = false;
// //
Toastify({ Toastify({
text: $_("created-blanco-cards"), text: $_("created-blanco-cards"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
}) })
.catch((err) => { .catch((err) => {
// //
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
// //
toast.hideToast(); toast.hideToast();
}); });
} }
} }
</script>
function submit_with_print() {
{#if bulk_modal_open} if (processed_last_submit === true) {
<div processed_last_submit = false;
class="fixed z-10 inset-0 overflow-y-auto" const toast = Toastify({
use:focusTrap text: $_("creating-blanco-cards"),
use:clickOutside duration: -1,
on:click_outside={() => { }).showToast();
bulk_modal_open = false; RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, true)
}}> .then((result) => {
<div bulk_modal_open = false;
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"> Toastify({
<div text: $_("created-blanco-cards"),
class="absolute inset-0 bg-gray-500 opacity-75" duration: 500,
data-id="modal_backdrop" /> backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
</div> }).showToast();
<span current_cards = current_cards.concat(result);
class="hidden sm:inline-block sm:align-middle sm:h-screen" const toast = Toastify({
aria-hidden="true">&#8203;</span> text: $_("generating-pdf"),
<div duration: -1,
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" }).showToast();
role="dialog" fetch(
aria-modal="true" `${config.baseurl_documentserver}/cards?&download=true&key=${config.documentserver_key}`,
aria-labelledby="modal-headline"> {
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> method: "POST",
<div class="sm:flex sm:items-start"> headers: {
<div "Content-Type": "application/json",
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 body: JSON.stringify(result),
class="h-6 w-6 text-blue-600" }
fill="currentColor" )
xmlns="http://www.w3.org/2000/svg" .then((response) => {
viewBox="0 0 24 24" if (response.status != "200") {
width="24" toast.hideToast();
height="24"><path fill="none" d="M0 0h24v24H0z" /> Toastify({
<path text: $_("pdf-generation-failed"),
fill="currentColor" duration: 3500,
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> backgroundColor:
</div> "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> }).showToast();
<h3 class="text-lg leading-6 font-medium text-gray-900"> } else {
{$_('create-bulk-blanco-cards')} return response.blob();
</h3> }
<div class="mt-2 mb-6"> })
<p class="text-sm text-gray-500"> .then((blob) => {
{$_('just-enter-how-many-you-want-and-the-system-will-create-them')} const url = window.URL.createObjectURL(blob);
</p> let a = document.createElement("a");
</div> a.href = url;
<div class="grid grid-cols-6 gap-6"> a.download = "Bulkcards.pdf";
<div class="col-span-6"> document.body.appendChild(a);
<label a.click();
for="amount" a.remove();
class="block text-sm font-medium text-gray-700">{$_('amount')}</label> toast.hideToast();
<div class="mt-1 flex rounded-md shadow-sm"> Toastify({
<input text: $_("pdf-successfully-generated"),
autocomplete="off" duration: 3500,
class:border-red-500={!is_card_count_valid} backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
class:focus:border-red-500={!is_card_count_valid} }).showToast();
class:focus:ring-red-500={!is_card_count_valid} })
bind:value={card_count} .catch((err) => {
type="number" console.error(err);
step="1" });
name="amount" })
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" .catch((err) => {
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">{$_('cards')}</span> .finally(() => {
</div> processed_last_submit = true;
{#if !is_card_count_valid} //
<span toast.hideToast();
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> });
{$_('you-must-create-at-least-one-card-or-cancel')} }
</span> }
{/if} </script>
</div>
</div> {#if bulk_modal_open}
</div> <div
</div> class="fixed z-10 inset-0 overflow-y-auto"
</div> use:focusTrap
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> use:clickOutside
<button on:click_outside={() => {
disabled={!createbtnenabled} bulk_modal_open = false;
class:opacity-50={!createbtnenabled} }}>
on:click={submit} <div
type="button" class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
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"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
{$_('create')} <div
</button> class="absolute inset-0 bg-gray-500 opacity-75"
<button data-id="modal_backdrop" />
on:click={() => { </div>
bulk_modal_open = false; <span
}} class="hidden sm:inline-block sm:align-middle sm:h-screen"
type="button" aria-hidden="true">&#8203;</span>
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"> <div
{$_('cancel')} 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-2xl sm:w-full"
</button> role="dialog"
</div> aria-modal="true"
</div> aria-labelledby="modal-headline">
</div> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
</div> <div class="sm:flex sm:items-start">
{/if} <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w- 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="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-bulk-blanco-cards')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('just-enter-how-many-you-want-and-the-system-will-create-them')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="amount"
class="block text-sm font-medium text-gray-700">{$_('amount')}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_card_count_valid}
class:focus:border-red-500={!is_card_count_valid}
class:focus:ring-red-500={!is_card_count_valid}
bind:value={card_count}
type="number"
step="1"
name="amount"
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">{$_('cards')}</span>
</div>
{#if !is_card_count_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('you-must-create-at-least-one-card-or-cancel')}
</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_with_print}
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-and-generate-pdf')}
</button>
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit_without_print}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-400 text-base font-medium text-white hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create-without-pdf')}
</button>
<button
on:click={() => {
bulk_modal_open = false;
}}
type="button"
class="mr-auto 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

@@ -1,170 +1,170 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap"; import { focusTrap } from "svelte-focus-trap";
import { import {
RunnerCardService, RunnerCardService,
RunnerService, RunnerService,
ScanService, ScanService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Select from "svelte-select"; import Select from "svelte-select";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let modal_open; export let modal_open;
export let current_cards; export let current_cards;
const getRunnerLabel = (option) => const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) => const filterRunners = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) || label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase()); option.value.toString().startsWith(filterText.toLowerCase());
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
$: runner = 0; $: runner = 0;
$: runners = []; $: runners = [];
$: enabled = true; $: enabled = true;
$: processed_last_submit = true; $: processed_last_submit = true;
RunnerService.runnerControllerGetAll().then((val) => { RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => { runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r }; return { label: getRunnerLabel(r), value: r };
}); });
}); });
$: createbtnenabled = true; $: createbtnenabled = true;
(() => { (() => {
document.onkeydown = (e) => { document.onkeydown = (e) => {
e = e || window.event; e = e || window.event;
if (e.key === "Escape") { if (e.key === "Escape") {
modal_open = false; modal_open = false;
} }
if (e.keyCode === 13) { if (e.keyCode === 13) {
if (createbtnenabled === true) { if (createbtnenabled === true) {
createbtnenabled = false; createbtnenabled = false;
submit(); submit();
} }
} }
}; };
})(); })();
function submit() { function submit() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: $_("adding-card"), text: $_("adding-card"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
let postdata = { let postdata = {
runner, runner,
enabled, enabled,
}; };
RunnerCardService.runnerCardControllerPost(postdata) RunnerCardService.runnerCardControllerPost(postdata)
.then((result) => { .then((result) => {
runner = 0; runner = 0;
modal_open = false; modal_open = false;
// //
Toastify({ Toastify({
text: $_("card-added"), text: $_("card-added"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
current_cards.push(result); current_cards.push(result);
current_cards = current_cards; current_cards = current_cards;
}) })
.catch((err) => { .catch((err) => {
// //
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
// //
toast.hideToast(); toast.hideToast();
}); });
} }
} }
</script> </script>
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap use:focusTrap
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;
}}> }}>
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> 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="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" /> data-id="modal_backdrop" />
</div> </div>
<span <span
class="hidden sm:inline-block sm:align-middle sm:h-screen" class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span> aria-hidden="true">&#8203;</span>
<div <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" class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline"> aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <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="sm:flex sm:items-start">
<div <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg <svg
class="h-6 w-6 text-blue-600" class="h-6 w-6 text-blue-600"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <path
fill="currentColor" fill="currentColor"
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-card')} {$_('create-a-new-card')}
</h3> </h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_('you-can-provide-a-runner-but-you-dont-have-to')} {$_('you-can-provide-a-runner-but-you-dont-have-to')}
{$_('if-you-want-to-create-multiple-blanco-cards-try-the-add-bulk-button')} {$_('if-you-want-to-create-multiple-blanco-cards-try-the-add-bulk-button')}
</p> </p>
</div> </div>
<div class="grid grid-cols-6 gap-6"> <div class="grid grid-cols-6 gap-6">
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="donor" for="donor"
class="block text-sm font-medium text-gray-700">{$_('runner')}</label> class="block text-sm font-medium text-gray-700">{$_('runner')}</label>
<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" 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)} itemFilter={(label, filterText, option) => filterRunners(label, filterText, option)}
items={runners} items={runners}
showChevron={true} showChevron={true}
placeholder={$_('search-for-runner-by-name-or-id')} placeholder={$_('search-for-runner-by-name-or-id')}
noOptionsMessage={$_('no-runners-found')} noOptionsMessage={$_('no-runners-found')}
on:select={(selectedValue) => (runner = selectedValue.detail.value.id)} on:select={(selectedValue) => (runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)} /> on:clear={() => (runner = null)} />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button <button
disabled={!createbtnenabled} disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled} class:opacity-50={!createbtnenabled}
on:click={submit} on:click={submit}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')} {$_('create')}
</button> </button>
<button <button
on:click={() => { on:click={() => {
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')} {$_('cancel')}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/if} {/if}

View File

@@ -1,193 +1,186 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap"; import { focusTrap } from "svelte-focus-trap";
import { RunnerCardService, RunnerService } from "@odit/lfk-client-js"; import { RunnerCardService, RunnerService } from "@odit/lfk-client-js";
import Select from "svelte-select"; import Select from "svelte-select";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let edit_modal_open; export let edit_modal_open;
export let current_cards; export let current_cards;
export let runner = {}; export let runner = {};
export let editable = {}; export let editable = {};
export let original_data = {}; export let original_data = {};
const getRunnerLabel = (option) => const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) => const filterRunners = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) || label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase()); option.value.toString().startsWith(filterText.toLowerCase());
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
$: runners = []; $: runners = [];
$: enabled = true; $: enabled = true;
$: processed_last_submit = true; $: processed_last_submit = true;
RunnerService.runnerControllerGetAll().then((val) => { RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => { runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r }; return { label: getRunnerLabel(r), value: r };
}); });
}); });
$: createbtnenabled = !( $: createbtnenabled = !(
JSON.stringify(editable) === JSON.stringify(original_data) JSON.stringify(editable) === JSON.stringify(original_data)
); );
(() => { (() => {
document.onkeydown = (e) => { document.onkeydown = (e) => {
e = e || window.event; e = e || window.event;
if (e.key === "Escape") { if (e.key === "Escape") {
edit_modal_open = false; edit_modal_open = false;
} }
if (e.keyCode === 13) { if (e.keyCode === 13) {
if (createbtnenabled === true) { if (createbtnenabled === true) {
createbtnenabled = false; createbtnenabled = false;
submit(); submit();
} }
} }
}; };
})(); })();
function submit() { function submit() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: $_("updating-card"), text: $_("updating-card"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
RunnerCardService.runnerCardControllerPut(original_data.id, editable) RunnerCardService.runnerCardControllerPut(original_data.id, editable)
.then((result) => { .then((result) => {
let id = original_data.id; let id = original_data.id;
runner = {}; runner = {};
editable = {}; editable = {};
original_data = {}; original_data = {};
edit_modal_open = false; edit_modal_open = false;
// //
Toastify({ Toastify({
text: $_("card-updated"), text: $_("card-updated"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
console.log( current_cards[current_cards.findIndex((c) => c.id === id)] = result;
JSON.stringify( current_cards = current_cards;
current_cards })
) .catch((err) => {
); //
current_cards[ })
current_cards.findIndex((c) => c.id === id) .finally(() => {
] = result; processed_last_submit = true;
current_cards = current_cards; //
}) toast.hideToast();
.catch((err) => { });
// }
}) }
.finally(() => { </script>
processed_last_submit = true;
// {#if edit_modal_open}
toast.hideToast(); <div
}); class="fixed z-10 inset-0 overflow-y-auto"
} use:focusTrap
} use:clickOutside
</script> on:click_outside={() => {
edit_modal_open = false;
{#if edit_modal_open} }}>
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
use:focusTrap <div class="fixed inset-0 transition-opacity" aria-hidden="true">
use:clickOutside <div
on:click_outside={() => { class="absolute inset-0 bg-gray-500 opacity-75"
edit_modal_open = false; data-id="modal_backdrop" />
}}> </div>
<div <span
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> class="hidden sm:inline-block sm:align-middle sm:h-screen"
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> aria-hidden="true">&#8203;</span>
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" 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"
data-id="modal_backdrop" /> role="dialog"
</div> aria-modal="true"
<span aria-labelledby="modal-headline">
class="hidden sm:inline-block sm:align-middle sm:h-screen" <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
aria-hidden="true">&#8203;</span> <div class="sm:flex sm:items-start">
<div <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" 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">
role="dialog" <svg
aria-modal="true" class="h-6 w-6 text-blue-600"
aria-labelledby="modal-headline"> fill="currentColor"
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> xmlns="http://www.w3.org/2000/svg"
<div class="sm:flex sm:items-start"> viewBox="0 0 24 24"
<div width="24"
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"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="h-6 w-6 text-blue-600" fill="currentColor"
fill="currentColor" d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
xmlns="http://www.w3.org/2000/svg" </div>
viewBox="0 0 24 24" <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
width="24" <h3 class="text-lg leading-6 font-medium text-gray-900">
height="24"><path fill="none" d="M0 0h24v24H0z" /> {$_('edit-a-card')}
<path </h3>
fill="currentColor" <div class="mt-2 mb-6">
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> <p class="text-sm text-gray-500">
</div> {$_('you-can-provide-a-runner-but-you-dont-have-to')}
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> </p>
<h3 class="text-lg leading-6 font-medium text-gray-900"> </div>
{$_('edit-a-card')} <div class="grid grid-cols-6 gap-6">
</h3> <div class="col-span-6">
<div class="mt-2 mb-6"> <label
<p class="text-sm text-gray-500"> for="runner"
{$_('you-can-provide-a-runner-but-you-dont-have-to')} class="block text-sm font-medium text-gray-700">{$_('runner')}</label>
</p> <Select
</div> 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"
<div class="grid grid-cols-6 gap-6"> itemFilter={(label, filterText, option) => filterRunners(label, filterText, option)}
<div class="col-span-6"> items={runners}
<label showChevron={true}
for="runner" placeholder={$_('search-for-runner-by-name-or-id')}
class="block text-sm font-medium text-gray-700">{$_('runner')}</label> noOptionsMessage={$_('no-runners-found')}
<Select bind:selectedValue={runner}
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" on:select={(selectedValue) => (editable.runner = selectedValue.detail.value.id)}
itemFilter={(label, filterText, option) => filterRunners(label, filterText, option)} on:clear={() => (editable.runner = null)} />
items={runners} </div>
showChevron={true} <div class="col-span-6">
placeholder={$_('search-for-runner-by-name-or-id')} <p class="text-gray-500">
noOptionsMessage={$_('no-runners-found')} <input
bind:selectedValue={runner} id="enabled"
on:select={(selectedValue) => (editable.runner = selectedValue.detail.value.id)} on:change={() => {
on:clear={() => (editable.runner = null)} /> editable.enabled = !editable.enabled;
</div> }}
<div class="col-span-6"> name="enabled"
<p class="text-gray-500"> type="checkbox"
<input checked={editable.enabled}
id="enabled" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
on:change={() => { {$_('this-card-is')}
editable.enabled = !editable.enabled; {#if editable.enabled}
}} {$_('enabled')}
name="enabled" {:else}{$_('disabled')}{/if}
type="checkbox" </p>
checked={editable.enabled} </div>
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> </div>
{$_('this-card-is')} </div>
{#if editable.enabled} </div>
{$_('enabled')} </div>
{:else}{$_('disabled')}{/if} <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
</p> <button
</div> disabled={!createbtnenabled}
</div> class:opacity-50={!createbtnenabled}
</div> on:click={submit}
</div> type="button"
</div> 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">
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> {$_('save-changes')}
<button </button>
disabled={!createbtnenabled} <button
class:opacity-50={!createbtnenabled} on:click={() => {
on:click={submit} edit_modal_open = false;
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"> type="button"
{$_('save-changes')} 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">
</button> {$_('cancel')}
<button </button>
on:click={() => { </div>
edit_modal_open = false; </div>
}} </div>
type="button" </div>
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"> {/if}
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -1,40 +1,40 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import store from "../../store"; import store from "../../store";
import AddCardBulkModal from "./AddCardBulkModal.svelte"; import AddCardBulkModal from "./AddCardBulkModal.svelte";
import AddCardModal from "./AddCardModal.svelte"; import AddCardModal from "./AddCardModal.svelte";
import CardsOverview from "./CardsOverview.svelte"; import CardsOverview from "./CardsOverview.svelte";
$: current_cards = []; $: current_cards = [];
export let modal_open = false; export let modal_open = false;
export let bulk_modal_open = false; export let bulk_modal_open = false;
</script> </script>
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('cards')} {$_('cards')}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:CREATE')}
<button <button
on:click={() => { on:click={() => {
modal_open = true; modal_open = true;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('add-card')} {$_('add-card')}
</button> </button>
<button <button
on:click={() => { on:click={() => {
bulk_modal_open = true; bulk_modal_open = true;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create-bulk-cards')} {$_('create-bulk-cards')}
</button> </button>
{/if} {/if}
</span> </span>
<CardsOverview bind:current_cards /> <CardsOverview bind:current_cards />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:CREATE')}
<AddCardModal bind:current_cards bind:modal_open /> <AddCardModal bind:current_cards bind:modal_open />
<AddCardBulkModal bind:current_cards bind:bulk_modal_open /> <AddCardBulkModal bind:current_cards bind:bulk_modal_open />
{/if} {/if}

View File

@@ -1,12 +1,12 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import cards_empty from "./cards.svg"; import cards_empty from "./cards.svg";
</script> </script>
<div class="text-center items-center justify-center"> <div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500"> <p class="mb-16 text-lg text-gray-500">
<img class="m-auto" style="height:15rem" src={cards_empty} alt="" /> <img class="m-auto" style="height:15rem" src={cards_empty} alt="" />
<span class="font-bold">{$_('there-are-no-cards-yet')}</span><br /> <span class="font-bold">{$_('there-are-no-cards-yet')}</span><br />
<span>{$_('add-your-first-card')}</span> <span>{$_('add-your-first-card')}</span>
</p> </p>
</div> </div>

View File

@@ -1,205 +1,281 @@
<script> <script>
import { getLocaleFromNavigator, json, _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { RunnerCardService } from "@odit/lfk-client-js"; import { RunnerCardService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import CardsEmptyState from "./CardsEmptyState.svelte"; import CardsEmptyState from "./CardsEmptyState.svelte";
import CardDetailModal from "./CardDetailModal.svelte"; import CardDetailModal from "./CardDetailModal.svelte";
export let edit_modal_open = false; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
export let runner = {}; export let edit_modal_open = false;
export let editable = {}; export let runner = {};
export let original_data = {}; export let editable = {};
$: searchvalue = ""; export let original_data = {};
$: active_deletes = []; export let current_cards = [];
export let current_cards = []; $: filtered_cards = current_cards.filter(function (c) {
const cards_promise = RunnerCardService.runnerCardControllerGetAll().then( if (
(val) => { c.code.toLowerCase().includes(searchvalue_lowercase) ||
current_cards = val; c.runner?.firstname.toLowerCase().includes(searchvalue_lowercase) ||
} c.runner?.middlename.toLowerCase().includes(searchvalue_lowercase) ||
); c.runner?.lastname.toLowerCase().includes(searchvalue_lowercase) ||
function should_display_based_on_id(id) { should_display_based_on_id(c.id)
if (searchvalue.toString().slice(-1) === "*") { ) {
return id.toString().startsWith(searchvalue.replace("*", "")); return true;
} }
return id.toString() === searchvalue; });
} $: searchvalue = "";
const getRunnerLabel = (option) => $: searchvalue_lowercase = searchvalue.toLowerCase();
option?.firstname + " " + (option?.middlename || "") + " " + (option?.lastname || "{$_('non-blanko')}"); $: active_deletes = [];
function open_edit_modal(card) { $: cards_show = current_cards.some((r) => r.is_selected === true);
if(card.runner?.id){ $: generate_cards = current_cards.filter((r) => r.is_selected === true);
runner = Object.assign( const cards_promise = RunnerCardService.runnerCardControllerGetAll().then(
{ runner }, (val) => {
{ label: getRunnerLabel(card.runner), value: card.runner } current_cards = val;
); }
card.runner = card.runner.id; );
} function should_display_based_on_id(id) {
else{ if (searchvalue.toString().slice(-1) === "*") {
card.runner=null; return id.toString().startsWith(searchvalue.replace("*", ""));
runner = null }
} return id.toString() === searchvalue;
editable = Object.assign(editable, card); }
original_data = Object.assign(original_data, card); const getRunnerLabel = (option) =>
edit_modal_open = true; option?.firstname + " " + (option?.middlename || "") + " " + (option?.lastname || "{$_('non-blanko')}");
} function open_edit_modal(card) {
</script> if(card.runner?.id){
runner = Object.assign(
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:UPDATE')} { runner },
<CardDetailModal { label: getRunnerLabel(card.runner), value: card.runner }
bind:current_cards );
bind:edit_modal_open card.runner = card.runner.id;
bind:runner }
bind:editable else{
bind:original_data /> card.runner=null;
{/if} runner = null
}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')} editable = Object.assign(editable, card);
{#await cards_promise} original_data = Object.assign(original_data, card);
<div edit_modal_open = true;
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">{$_('loading-cards')}</p> let scrollTop = 0;
<p class="text-sm">{$_('this-might-take-a-moment')}</p> $: rendered = filtered_cards;
</div> let innerHeight = 0;
{:then} let ele;
{#if current_cards.length === 0} $: updateSlice(scrollTop);
<CardsEmptyState /> $: innerHeight = `${filtered_cards.length * 25}px`;
{:else} $: if (ele) updateSlice();
<input function updateSlice() {
type="search" const height = ele ? parseInt(ele.clientHeight) : 100;
bind:value={searchvalue} const init = scrollTop / 25;
placeholder={$_('datatable.search')} const end = Math.ceil((scrollTop + height) / 25);
aria-label={$_('datatable.search')} rendered = filtered_cards.slice(init, end + 15);
class="gridjs-input gridjs-search-input mb-4" /> }
<div function updateScroll($event) {
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> scrollTop = $event.target.scrollTop;
<table class="divide-y divide-gray-200 w-full"> }
<thead class="bg-gray-50"> </script>
<tr>
<th <style>
scope="col" table tbody {
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> display: block;
{$_('code')} overflow-y: scroll;
</th> }
<th
scope="col" table thead, table tbody tr {
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> display: table;
{$_('runner')} width: 100%;
</th> table-layout: fixed;
<th }
scope="col" </style>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('status')} {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:UPDATE')}
</th> <CardDetailModal
<th scope="col" class="relative px-6 py-3"> bind:current_cards
<span class="sr-only">{$_('action')}</span> bind:edit_modal_open
</th> bind:runner
</tr> bind:editable
</thead> bind:original_data />
<tbody class="divide-y divide-gray-200"> {/if}
{#each current_cards as card}
{#if card.code {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
.toLowerCase() {#await cards_promise}
.includes( <div
searchvalue.toLowerCase() class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
) || card.runner?.firstname role="alert">
.toLowerCase() <p class="font-bold">{$_('loading-cards')}</p>
.includes( <p class="text-sm">{$_('this-might-take-a-moment')}</p>
searchvalue.toLowerCase() </div>
) || card.runner?.middlename {:then}
.toLowerCase() {#if current_cards.length === 0}
.includes( <CardsEmptyState />
searchvalue.toLowerCase() {:else}
) || card.runner?.lastname <input
.toLowerCase() type="search"
.includes( bind:value={searchvalue}
searchvalue.toLowerCase() placeholder={$_('datatable.search')}
) || should_display_based_on_id(card.id)} aria-label={$_('datatable.search')}
<tr data-rowid="card_{card.id}"> class="gridjs-input gridjs-search-input mb-4" />
<td class="px-6 py-4 whitespace-nowrap"> <div class="h-12">
<div class="flex items-center">{card.code}</div> <GenerateRunnerCards
</td> bind:cards_show
<td class="px-6 py-4 whitespace-nowrap"> bind:generate_cards />
<div class="flex items-center"> </div>
{#if card.runner} <div
<a class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
href="../runners/{card.runner.id}" <table class="divide-y divide-gray-200 w-full">
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{card.runner.firstname} <thead class="bg-gray-50">
{card.runner.middlename || ''} <tr>
{card.runner.lastname}</a> <th
{:else}{$_('non-blanko')}{/if} scope="col"
</div> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</td> <span
<td class="px-6 py-4 whitespace-nowrap"> on:click={() => {
<div class="flex items-center"> const newstate = !current_cards.some((r) => r.is_selected === true);
{#if card.enabled} current_cards = current_cards.map((r) => {
<span r.is_selected = newstate;
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('enabled_large')}</span> return r;
{:else} });
<span }}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('disabled')}</span> class="underline cursor-pointer select-none">{#if current_cards.some((r) => r.is_selected === true)}
{/if} {$_('deselect-all')}
</div> {:else}{$_('select-all')}{/if}
</td> </span>
</th>
{#if active_deletes[card.id] === true} <th
<td scope="col"
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<button {$_('code')}
on:click={() => { </th>
active_deletes[card.id] = false; <th
}} scope="col"
tabindex="0" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> {$_('runner')}
<button </th>
on:click={() => { <th
RunnerCardService.runnerCardControllerRemove(card.id, false).then( scope="col"
(resp) => { class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
current_cards = current_cards.filter( {$_('status')}
(obj) => obj.id !== card.id </th>
); <th scope="col" class="relative px-6 py-3">
Toastify({ <span class="sr-only">{$_('action')}</span>
text: $_('card-deleted'), </th>
duration: 500, </tr>
backgroundColor: </thead>
'linear-gradient(to right, #00b09b, #96c93d)', <tbody class="divide-y divide-gray-200 virtual-wrapper"
}).showToast(); on:scroll={updateScroll}
} style="height: 70vh; width:100%"
); bind:this={ele}
}} >
tabindex="0" {#each filtered_cards as card, index}
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> {#if card.code
</td> .toLowerCase()
{:else} .includes(
<td searchvalue.toLowerCase()
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> ) || card.runner?.firstname
<button .toLowerCase()
on:click={() => { .includes(
open_edit_modal(card); searchvalue.toLowerCase()
}} ) || card.runner?.middlename
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</button> .toLowerCase()
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:DELETE')} .includes(
<button searchvalue.toLowerCase()
on:click={() => { ) || card.runner?.lastname
active_deletes[card.id] = true; .toLowerCase()
}} .includes(
tabindex="0" searchvalue.toLowerCase()
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> ) || should_display_based_on_id(card.id)}
{/if} <tr data-rowid="card_{card.id}">
</td> <td class="px-6 py-4 whitespace-nowrap">
{/if} <input
</tr> bind:checked={card.is_selected}
{/if} type="checkbox"
{/each} class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</tbody> </td>
</table> <td class="px-6 py-4 whitespace-nowrap">
</div> <div class="flex items-center">{card.code}</div>
{/if} </td>
{:catch error} <td class="px-6 py-4 whitespace-nowrap">
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> <div class="flex items-center">
<span class="inline-block align-middle mr-8"> {#if card.runner}
<b class="capitalize">{$_('general_promise_error')}</b> <a
{error} href="../runners/{card.runner.id}"
</span> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{card.runner.firstname}
</div> {card.runner.middlename || ''}
{/await} {card.runner.lastname}</a>
{/if} {:else}{$_('non-blanko')}{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if card.enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('enabled')}</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('disabled')}</span>
{/if}
</div>
</td>
{#if active_deletes[card.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[card.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
RunnerCardService.runnerCardControllerRemove(card.id, false).then(
(resp) => {
current_cards = current_cards.filter(
(obj) => obj.id !== card.id
);
Toastify({
text: $_('card-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">
<button
on:click={() => {
open_edit_modal(card);
}}
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</button>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:DELETE')}
<button
on:click={() => {
active_deletes[card.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

@@ -86,7 +86,7 @@
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: "Contact is being added...", text: $_('contact-is-being-added'),
duration: -1, duration: -1,
}).showToast(); }).showToast();
let address = {}; let address = {};
@@ -123,7 +123,7 @@
modal_open = false; modal_open = false;
// //
Toastify({ Toastify({
text: "Contact added", text: $_('contact-added'),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();

View File

@@ -1,308 +1,341 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import localForage from "localforage"; import localForage from "localforage";
import store from "../../store"; import store from "../../store";
import { router } from "tinro"; import { router } from "tinro";
import NoComponentLoaded from "../base/NoComponentLoaded.svelte"; import NoComponentLoaded from "../base/NoComponentLoaded.svelte";
import { AuthService } from "@odit/lfk-client-js"; import { AuthService } from "@odit/lfk-client-js";
$: navOpen = false; $: navOpen = false;
function logout() { function logout() {
localForage.clear(); localForage.clear();
location.replace("/"); location.replace("/");
} }
</script> </script>
<section class="min-h-screen bg-gray-50"> <style>
<nav .collapsed_navigation {
class:-translate-x-full={!navOpen} transform: translateX(-100%);
class:translate-x-0={navOpen} }
class="select-none fixed top-0 left-0 z-20 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 md:translate-x-0 bg-gray-50"> @media (min-width: 768px) {
<a href="/" class="flex items-center px-4 py-5"> .collapsed_navigation {
<img src="/lfk-logo.png" alt="Logo" class="h-10" /> transform: translateX(0px);
<h3 class="text-lg">Lauf für Kaya! Admin</h3> }
</a> }
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation"> </style>
<a
class:bg-gray-100={$router.path === '/'} <section class="min-h-screen bg-gray-50">
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" <div
href="/"> class:collapsed_navigation={!navOpen}
<svg class="select-none fixed top-0 left-0 z-20 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 bg-gray-50">
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" <a href="/" class="flex items-center px-4 py-5">
xmlns="http://www.w3.org/2000/svg" <img src="/lfk-logo.png" alt="Logo" class="h-10" />
viewBox="0 0 20 20" <h3 class="text-lg">Lauf für Kaya! Admin</h3>
fill="currentColor"> </a>
<path <nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation">
d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" /> <a
</svg> class:bg-gray-100={$router.path === '/'}
<span>{$_('dashboard-title')}</span> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
</a> href="/">
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')} <svg
<a class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
class:bg-gray-100={$router.path.includes('/orgs/')} xmlns="http://www.w3.org/2000/svg"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" viewBox="0 0 20 20"
href="/orgs/"> fill="currentColor">
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" />
fill="currentColor" </svg>
xmlns="http://www.w3.org/2000/svg" <span>{$_('dashboard-title')}</span>
viewBox="0 0 24 24" </a>
width="24" {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
height="24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path.includes('/orgs/')}
d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('orgs')}</span> href="/orgs/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')} fill="currentColor"
<a xmlns="http://www.w3.org/2000/svg"
class:bg-gray-100={$router.path === '/users/'} viewBox="0 0 24 24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" width="24"
href="/users/"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
xmlns="http://www.w3.org/2000/svg" d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg>
width="24" <span>{$_('orgs')}</span>
height="24" </a>
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" {/if}
fill="currentColor" {#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')}
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path === '/users/'}
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> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('users')}</span> href="/users/">
</a> <svg
{/if} xmlns="http://www.w3.org/2000/svg"
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')} width="24"
<a height="24"
class:bg-gray-100={$router.path === '/groups/'} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" fill="currentColor"
href="/groups/"> viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" 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>
fill="currentColor" <span>{$_('users')}</span>
width="24" </a>
height="24" {/if}
xmlns="http://www.w3.org/2000/svg" {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')}
viewBox="0 0 640 512"><path <a
fill="currentColor" class:bg-gray-100={$router.path === '/groups/'}
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('user-groups')}</span> href="/groups/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} fill="currentColor"
<a width="24"
class:bg-gray-100={$router.path === '/runners/'} height="24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" xmlns="http://www.w3.org/2000/svg"
href="/runners/"> viewBox="0 0 640 512"><path
<svg fill="currentColor"
xmlns="http://www.w3.org/2000/svg" d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
viewBox="0 0 24 24" <span>{$_('user-groups')}</span>
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" </a>
fill="currentColor" {/if}
width="24" {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
height="24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path === '/runners/'}
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('runners')}</span> href="/runners/">
</a> <svg
{/if} xmlns="http://www.w3.org/2000/svg"
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')} viewBox="0 0 24 24"
<a class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
class:bg-gray-100={$router.path === '/teams/'} fill="currentColor"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" width="24"
href="/teams/"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
fill="currentColor" <span>{$_('runners')}</span>
width="24" </a>
height="24" {/if}
xmlns="http://www.w3.org/2000/svg" {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
viewBox="0 0 640 512"><path <a
fill="currentColor" class:bg-gray-100={$router.path === '/teams/'}
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('teams')}</span> href="/teams/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')} fill="currentColor"
<a width="24"
class:bg-gray-100={$router.path.includes('/donors/')} height="24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" xmlns="http://www.w3.org/2000/svg"
href="/donors/"> viewBox="0 0 640 512"><path
<svg fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
fill="currentColor" <span>{$_('teams')}</span>
xmlns="http://www.w3.org/2000/svg" </a>
viewBox="0 0 24 24" {/if}
width="24" {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
height="24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path.includes('/donors/')}
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('donors')}</span> href="/donors/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')} fill="currentColor"
<a xmlns="http://www.w3.org/2000/svg"
class:bg-gray-100={$router.path.includes('/donations/')} viewBox="0 0 24 24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" width="24"
href="/donations/"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg>
fill="currentColor" <span>{$_('donors')}</span>
xmlns="http://www.w3.org/2000/svg" </a>
viewBox="0 0 24 24" {/if}
width="24" {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
height="24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path.includes('/donations/')}
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('donations')}</span> href="/donations/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')} fill="currentColor"
<a xmlns="http://www.w3.org/2000/svg"
class:bg-gray-100={$router.path === '/tracks/'} viewBox="0 0 24 24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" width="24"
href="/tracks/"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg>
fill="currentColor" <span>{$_('donations')}</span>
width="24" </a>
height="24" {/if}
xmlns="http://www.w3.org/2000/svg" {#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')}
viewBox="0 0 640 512"><path <a
fill="currentColor" class:bg-gray-100={$router.path === '/tracks/'}
d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" /></svg> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('tracks')}</span> href="/tracks/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')} fill="currentColor"
<a width="24"
class:bg-gray-100={$router.path === '/scans/'} height="24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" xmlns="http://www.w3.org/2000/svg"
href="/scans/"> viewBox="0 0 640 512"><path
<svg fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" /></svg>
fill="currentColor" <span>{$_('tracks')}</span>
width="24" </a>
height="24" {/if}
xmlns="http://www.w3.org/2000/svg" {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path === '/cards/'}
fill="currentColor" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> href="/cards/">
<span>Scans</span> <svg
</a> class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{/if} fill="currentColor"
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')} width="24"
<a height="24"
class:bg-gray-100={$router.path === '/contacts/'} xmlns="http://www.w3.org/2000/svg"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" viewBox="0 0 24 24">
href="/contacts/"> <path fill="none" d="M0 0h24v24H0z" />
<svg <path
fill="currentColor" fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
xmlns="http://www.w3.org/2000/svg" <span>{$_('cards')}</span>
viewBox="0 0 24 24" </a>
width="24" {/if}
height="24"><path fill="none" d="M0 0h24v24H0z" /> {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')}
<path <a
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> class:bg-gray-100={$router.path === '/scans/'}
<span>{$_('contacts')}</span> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
</a> href="/scans/">
{/if} <svg
{#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
<a fill="currentColor"
class:bg-gray-100={$router.path === '/scanstations/'} width="24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" height="24"
href="/scanstations/"> xmlns="http://www.w3.org/2000/svg"
<svg viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" <path
fill="currentColor" fill="currentColor"
width="24" d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg>
height="24" <span>Scans</span>
viewBox="0 0 24 24" </a>
xmlns="http://www.w3.org/2000/svg"><path {/if}
fill="none" {#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')}
d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path === '/contacts/'}
fill="currentColor" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> href="/contacts/">
<span>{$_('scanstations')}</span> <svg
</a> fill="currentColor"
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
<a xmlns="http://www.w3.org/2000/svg"
class:bg-gray-100={$router.path === '/settings/'} viewBox="0 0 24 24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" width="24"
href="/settings/"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" 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>
xmlns="http://www.w3.org/2000/svg" <span>{$_('contacts')}</span>
viewBox="0 0 20 20" </a>
fill="currentColor"> {/if}
<path {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')}
fill-rule="evenodd" <a
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" class:bg-gray-100={$router.path === '/scanstations/'}
clip-rule="evenodd" /> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
</svg> href="/scanstations/">
<span>{$_('settings')}</span> <svg
</a> class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
<a fill="currentColor"
class:bg-gray-100={$router.path === '/about/'} width="24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" height="24"
href="/about/"> viewBox="0 0 24 24"
<svg xmlns="http://www.w3.org/2000/svg"><path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" fill="none"
xmlns="http://www.w3.org/2000/svg" d="M0 0h24v24H0z" />
fill="none" <path
stroke="currentColor" fill="currentColor"
stroke-width="2" d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
stroke-linecap="round" <span>{$_('scanstations')}</span>
stroke-linejoin="round" </a>
viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" /> {/if}
<path d="M12 16v-4M12 8h.01" /></svg> <a
<span>{$_('about')}</span> class:bg-gray-100={$router.path === '/settings/'}
</a> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span href="/settings/">
tabindex="0" <svg
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
on:click={() => { xmlns="http://www.w3.org/2000/svg"
AuthService.authControllerLogout(); viewBox="0 0 20 20"
logout(); fill="currentColor">
}}> <path
<svg fill-rule="evenodd"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
fill="currentColor" clip-rule="evenodd" />
width="24" </svg>
height="24" <span>{$_('settings')}</span>
xmlns="http://www.w3.org/2000/svg" </a>
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path === '/about/'}
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z" /></svg> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('logout')}</span> href="/about/">
</span> <svg
</nav> class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
</nav> xmlns="http://www.w3.org/2000/svg"
<div class="ml-0 transition md:ml-60"> fill="none"
<header stroke="currentColor"
on:click={() => { stroke-width="2"
navOpen = true; stroke-linecap="round"
}} stroke-linejoin="round"
class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden"> viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
<button class="block btn btn-light md:hidden"> <path d="M12 16v-4M12 8h.01" /></svg>
<span class="sr-only">Menu</span><svg <span>{$_('about')}</span>
class="w-4 h-4" </a>
xmlns="http://www.w3.org/2000/svg" <span
viewBox="0 0 20 20" tabindex="0"
fill="currentcolor"><path class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
fill-rule="evenodd" on:click={() => {
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" AuthService.authControllerLogout();
clip-rule="evenodd" /></svg></button> logout();
</header> }}>
<slot> <svg
<NoComponentLoaded /> class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
</slot> fill="currentColor"
</div> width="24"
<div height="24"
on:click={() => { xmlns="http://www.w3.org/2000/svg"
navOpen = false; viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
}} <path
class:hidden={!navOpen} d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z" /></svg>
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" /> <span>{$_('logout')}</span>
</section> </span>
</nav>
</div>
<div class="ml-0 transition md:ml-60">
<header
class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden">
<button
on:click={() => {
navOpen = true;
}}
class="block btn btn-light md:hidden">
<span class="sr-only">Menu</span><svg
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentcolor"><path
fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clip-rule="evenodd" /></svg></button>
</header>
<slot>
<NoComponentLoaded />
</slot>
</div>
{#if navOpen === true}
<div
on:click={() => {
navOpen = false;
console.log({ navOpen });
}}
class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" />
{/if}
</section>

View File

@@ -9,6 +9,7 @@
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Select from "svelte-select"; import Select from "svelte-select";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { is_promise } from "svelte/internal";
export let modal_open; export let modal_open;
export let current_donations; export let current_donations;
const getDonorLabel = (option) => const getDonorLabel = (option) =>
@@ -24,6 +25,7 @@
$: donors = []; $: donors = [];
$: runners = []; $: runners = [];
$: is_fixed = false; $: is_fixed = false;
$: is_paid = false;
DonorService.donorControllerGetAll().then((val) => { DonorService.donorControllerGetAll().then((val) => {
donors = val.map((r) => { donors = val.map((r) => {
return { label: getDonorLabel(r), value: r }; return { label: getDonorLabel(r), value: r };
@@ -57,14 +59,18 @@
let amount_cent = Math.floor(amount_input * 100); let amount_cent = Math.floor(amount_input * 100);
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: "adding donation", text: $_('adding-donation'),
duration: -1, duration: -1,
}).showToast(); }).showToast();
if (is_fixed) { if (is_fixed) {
let postdata = { let postdata = {
donor, donor,
amount: amount_cent, amount: amount_cent,
paidAmount: 0
}; };
if(is_paid){
postdata.paidAmount = amount_cent;
}
DonationService.donationControllerPostFixed(postdata) DonationService.donationControllerPostFixed(postdata)
.then((result) => { .then((result) => {
donor = donors[0].id || 0; donor = donors[0].id || 0;
@@ -73,7 +79,7 @@
modal_open = false; modal_open = false;
// //
Toastify({ Toastify({
text: "donation_added", text: $_('donation_added'),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
@@ -102,7 +108,7 @@
modal_open = false; modal_open = false;
// //
Toastify({ Toastify({
text: "donation_added", text: $_('donation_added'),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
@@ -123,7 +129,7 @@
</script> </script>
<style> <style>
input:before { .toggle:before {
content: ""; content: "";
position: absolute; position: absolute;
width: 1.25rem; width: 1.25rem;
@@ -137,12 +143,12 @@
transition: 0.2s ease-in-out; transition: 0.2s ease-in-out;
} }
input:checked { .toggle:checked {
/* @apply: bg-indigo-400; */ /* @apply: bg-indigo-400; */
background-color: #7f9cf5; background-color: #7f9cf5;
} }
input:checked:before { .toggle:checked:before {
left: 1.25rem; left: 1.25rem;
} }
</style> </style>
@@ -195,7 +201,7 @@
class="ml-2 text-base" class="ml-2 text-base"
class:text-gray-300={is_fixed}>{$_('distance-donation')}</span> class:text-gray-300={is_fixed}>{$_('distance-donation')}</span>
<input <input
class="relative w-10 h-5 transition-all duration-200 ease-in-out bg-gray-400 rounded-full shadow-inner outline-none appearance-none align-middle" class="toggle relative w-10 h-5 transition-all duration-200 ease-in-out bg-gray-400 rounded-full shadow-inner outline-none appearance-none align-middle"
type="checkbox" type="checkbox"
bind:checked={is_fixed} /> bind:checked={is_fixed} />
<span <span
@@ -267,6 +273,29 @@
</span> </span>
{/if} {/if}
</div> </div>
{#if is_fixed}
<div class="col-span-6">
<label
for="paid"
class="block text-sm font-medium text-gray-700">{$_('already-paid')}</label>
<p class="text-gray-500">
<input
id="paid"
bind:checked={is_paid}
name="paid"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" >
<span class="align-text-bottom">
{#if is_paid}
{$_('paid')}
{:else}
{$_('open')}
{/if}
</span>
</p>
</div>
{/if}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,202 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { DonationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let payment_modal_open = false;
export let current_donations = [];
export let editable = {};
export let original_data = {};
export let paid_amount_input = 0;
$:processed_last_submit=true;
function focus(el) {
el.focus();
}
$: createbtnenabled = is_paid_amount_valid && !(paid_amount_input*100 == original_data.paidAmount)
$: is_paid_amount_valid = paid_amount_input > 0;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
payment_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: $_('updating-donation'),
duration: -1,
}).showToast();
editable.donor = editable.donor.id;
editable.paidAmount = paid_amount_input*100;
if(editable.responseType == "DISTANCEDONATION" || editable.runner){
editable.runner = editable.runner.id;
DonationService.donationControllerPutDistance(original_data.id, editable)
.then((result) => {
let id = original_data.id;
editable = {};
original_data = {};
payment_modal_open = false;
//
Toastify({
text: $_('donation-updated'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donations[current_donations.findIndex((c) => c.id === id)] = result;
current_donations = current_donations;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
else{
DonationService.donationControllerPutFixed(original_data.id, editable)
.then((result) => {
let id = original_data.id;
editable = {};
original_data = {};
payment_modal_open = false;
//
Toastify({
text: $_('donation-updated'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donations[current_donations.findIndex((c) => c.id === id)] = result;
current_donations = current_donations;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
}
</script>
{#if payment_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
payment_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="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('enter-payment')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('you-can-enter-the-donations-paid-amount-manually-or-use-the-max-button-to-use-the-donations-exact-amount')}
</p>
</div>
<div class="grid grid-cols gap-6">
<div class="w-full">
<label
for="token"
class="block text-sm font-medium text-gray-700">{$_('paid-amount')}</label>
<div class="inline-flex border-gray-300 border rounded-l-md rounded-r-md bg-gray-50 text-gray-500 w-full">
<input
autocomplete="off"
class:border-red-500={!is_paid_amount_valid}
class:focus:border-red-500={!is_paid_amount_valid}
class:focus:ring-red-500={!is_paid_amount_valid}
bind:value={paid_amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm p-2"
placeholder="2.00" />
<button
on:click={
()=>{
paid_amount_input=paid_amount_input = (original_data.amount/100).toFixed(2);
}
}
class="inline-flex items-center p-r-2 text-indigo-300 hover:text-indigo-700 text-sm">MAX</button>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"></span>
</div>
{#if !is_paid_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('payment-amount-must-be-greater-than-0-00eur')}
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('save-changes')}
</button>
<button
on:click={() => {
payment_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

@@ -20,6 +20,8 @@
$: current_runners = []; $: current_runners = [];
$: amount_input = 0; $: amount_input = 0;
$: is_amount_valid = amount_input > 0; $: is_amount_valid = amount_input > 0;
$: paid_amount_input = 0;
$: is_paid_amount_valid = paid_amount_input > 0;
$: is_everything_set = $: is_everything_set =
editable.donor != null && editable.donor != null &&
((original_data.responseType == "DISTANCEDONATION" && ((original_data.responseType == "DISTANCEDONATION" &&
@@ -30,15 +32,17 @@
(original_data.responseType == "DISTANCEDONATION" && (original_data.responseType == "DISTANCEDONATION" &&
!(Math.floor(amount_input * 100) === original_data.amountPerDistance)) || !(Math.floor(amount_input * 100) === original_data.amountPerDistance)) ||
(original_data.responseType !== "DISTANCEDONATION" && (original_data.responseType !== "DISTANCEDONATION" &&
!(Math.floor(amount_input * 100) === original_data.amount)); !(Math.floor(amount_input * 100) === original_data.amount)) ||
!(Math.floor(paid_amount_input * 100) === original_data.paidAmount);
$: save_enabled = changes_performed && is_amount_valid && is_everything_set; $: save_enabled = changes_performed && is_amount_valid && is_everything_set;
const promise = DonationService.donationControllerGetOne( const promise = DonationService.donationControllerGetOne(
params.donationid params.donationid
).then((data) => { ).then((data) => {
data_loaded = true; data_loaded = true;
original_data = Object.assign(original_data, data); original_data = Object.assign({}, data);
editable = Object.assign(editable, original_data); editable = Object.assign({}, original_data);
paid_amount_input = data.paidAmount / 100;
if (data.responseType == "DISTANCEDONATION") { if (data.responseType == "DISTANCEDONATION") {
amount_input = data.amountPerDistance / 100; amount_input = data.amountPerDistance / 100;
RunnerService.runnerControllerGetAll().then((val) => { RunnerService.runnerControllerGetAll().then((val) => {
@@ -66,10 +70,11 @@
function submit() { function submit() {
if (data_loaded === true && save_enabled) { if (data_loaded === true && save_enabled) {
Toastify({ Toastify({
text: "Donation is being updated", text: $_('updating-donation'),
duration: 2500, duration: 2500,
}).showToast(); }).showToast();
let postdata = {}; let postdata = {};
editable.paidAmount = paid_amount_input*100;
if (original_data.responseType === "DISTANCEDONATION") { if (original_data.responseType === "DISTANCEDONATION") {
editable.amountPerDistance = Math.floor(amount_input * 100); editable.amountPerDistance = Math.floor(amount_input * 100);
postdata = Object.assign(postdata, editable); postdata = Object.assign(postdata, editable);
@@ -83,7 +88,7 @@
Object.assign(original_data, editable); Object.assign(original_data, editable);
original_data = original_data; original_data = original_data;
Toastify({ Toastify({
text: "updated donation", text: $_('donation-updated'),
duration: 2500, duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
@@ -98,7 +103,7 @@
Object.assign(original_data, editable); Object.assign(original_data, editable);
original_data = original_data; original_data = original_data;
Toastify({ Toastify({
text: "updated donation", text: $_('donation-updated'),
duration: 2500, duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
@@ -112,7 +117,7 @@
DonationService.donationControllerRemove(original_data.id, false) DonationService.donationControllerRemove(original_data.id, false)
.then((resp) => { .then((resp) => {
Toastify({ Toastify({
text: "Donation delete", text: $_('donation-deleted'),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
@@ -219,7 +224,24 @@
<span>{(editable.amount / 100) <span>{(editable.amount / 100)
.toFixed(2) .toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</span> .toLocaleString('de-DE', { valute: 'EUR' })}</span>
|
<span
class="font-medium text-gray-700">{$_('paid-amount')}:</span>
<span>{(editable.paidAmount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</span>
|
<span
class="font-medium text-gray-700">{$_('status')}:</span>
{#if editable.status =="PAID"}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('paid')}</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('open')}</span>
{/if}
</div> </div>
<br>
<div class=" w-full"> <div class=" w-full">
<label <label
for="donor" for="donor"
@@ -232,7 +254,7 @@
placeholder={$_('search-for-donor-name-or-id')} placeholder={$_('search-for-donor-name-or-id')}
noOptionsMessage={$_('no-donors-found')} noOptionsMessage={$_('no-donors-found')}
bind:selectedValue={donor} bind:selectedValue={donor}
on:select={(selectedValue) => (editable.donor = selectedValue.detail.value)} on:select={(selectedValue) => {editable.donor = selectedValue.detail.value; editable.donor.donationAmount=original_data.donor.donationAmount; editable.donor.paidDonationAmount =original_data.donor.paidDonationAmount}}
on:clear={() => (editable.donor = null)} /> on:clear={() => (editable.donor = null)} />
</div> </div>
{#if original_data.responseType == 'DISTANCEDONATION'} {#if original_data.responseType == 'DISTANCEDONATION'}
@@ -280,6 +302,39 @@
</span> </span>
{/if} {/if}
</div> </div>
<div class="w-full">
<label
for="token"
class="block text-sm font-medium text-gray-700">{$_('paid-amount')}</label>
<div class="inline-flex border-gray-300 border rounded-l-md rounded-r-md bg-gray-50 text-gray-500 w-full">
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={paid_amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm p-2"
placeholder="2.00" />
<button
on:click={
()=>{
paid_amount_input=paid_amount_input = (original_data.amount/100).toFixed(2);
}
}
class="inline-flex items-center p-r-2 text-indigo-300 hover:text-indigo-700 text-sm">MAX</button>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"></span>
</div>
{#if !is_paid_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('payment-amount-must-be-greater-than-0-00eur')}
</span>
{/if}
</div>
</section> </section>
{:catch error} {:catch error}
<PromiseError {error} /> <PromiseError {error} />

View File

@@ -4,9 +4,14 @@
import store from "../../store"; import store from "../../store";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import DonationsEmptyState from "./DonationsEmptyState.svelte"; import DonationsEmptyState from "./DonationsEmptyState.svelte";
import AddDonationPaymentModal from "./AddDonationPaymentModal.svelte";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
export let current_donations = []; export let current_donations = [];
export let payment_modal_open = false;
export let editable = {};
export let original_data = {};
export let paid_amount_input = 0;
const donations_promise = DonationService.donationControllerGetAll().then( const donations_promise = DonationService.donationControllerGetAll().then(
(val) => { (val) => {
current_donations = val; current_donations = val;
@@ -18,8 +23,15 @@
} }
return id.toString() === searchvalue; return id.toString() === searchvalue;
} }
function open_payment_modal(donation) {
editable = Object.assign({}, donation);
original_data = Object.assign({}, donation);
paid_amount_input = (donation.paidAmount/100).toFixed(2);
payment_modal_open = true;
}
</script> </script>
<AddDonationPaymentModal bind:current_donations bind:original_data bind:editable bind:paid_amount_input bind:payment_modal_open />
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
{#await donations_promise} {#await donations_promise}
<div <div
@@ -63,6 +75,16 @@
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('donation-amount')} {$_('donation-amount')}
</th> </th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('paid-amount')}
</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"> <th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span> <span class="sr-only">{$_('action')}</span>
</th> </th>
@@ -132,6 +154,22 @@
.toLocaleString('de-DE', { valute: 'EUR' })} .toLocaleString('de-DE', { valute: 'EUR' })}
</div> </div>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
{(donation.paidAmount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donation.status =="PAID"}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('paid')}</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('open')}</span>
{/if}
</td>
{#if active_deletes[donation.id] === true} {#if active_deletes[donation.id] === true}
<td <td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
@@ -149,7 +187,7 @@
(obj) => obj.id !== donation.id (obj) => obj.id !== donation.id
); );
Toastify({ Toastify({
text: 'Donation deleted', text: $_('donation-deleted'),
duration: 500, duration: 500,
backgroundColor: backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)', 'linear-gradient(to right, #00b09b, #96c93d)',
@@ -163,6 +201,9 @@
{:else} {:else}
<td <td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {open_payment_modal(donation);}}
class="text-[#025a21] hover:text-green-900 mr-4">{$_('enter-payment')}</button>
<a <a
href="./{donation.id}" href="./{donation.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>

View File

@@ -19,7 +19,7 @@
) )
.then((resp) => { .then((resp) => {
Toastify({ Toastify({
text: "Donor deleted", text: $_('donor-deleted'),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();

View File

@@ -193,6 +193,12 @@
<span>{(editable.donationAmount / 100) <span>{(editable.donationAmount / 100)
.toFixed(2) .toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</span> .toLocaleString('de-DE', { valute: 'EUR' })}</span>
|
<span
class="font-medium text-gray-700">{$_('total-paid-amount')}:</span>
<span>{(editable.paidDonationAmount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</span>
<br /> <br />
<span class="font-medium text-gray-700">{$_('donations')}:</span> <span class="font-medium text-gray-700">{$_('donations')}:</span>
{#if current_donations.filter((d) => d.donor.id == editable.id).length > 0} {#if current_donations.filter((d) => d.donor.id == editable.id).length > 0}

View File

@@ -20,6 +20,31 @@
{$_('add-donor')} {$_('add-donor')}
</button> </button>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
<button
on:click={() => {
const data = (current_donors.filter(d=>d.receiptNeeded===true)).map(function (d) {
d.address.address2=d.address.address2===""?"":" "+d.address.address2;
const address=`${d.address.address1}${d.address.address2}, ${d.address.postalcode} ${d.address.city}, ${d.address.country}`;
return [d.firstname,d.middlename,d.lastname,d.paidDonationAmount,address];
})
let csv = `${$_('csv_import__firstname')};${$_('csv_import__middlename')};${$_('csv_import__lastname')};${$_('total_donation_amount_in_eur')};${$_('address')}\n`;
data.forEach(function(row) {
csv += row.join(';');
csv += "\n";
});
let hiddenElement = document.createElement('a');
hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
hiddenElement.target = '_blank';
hiddenElement.download = `${$_('filename_sponsoringquittungsliste')}.csv`;
hiddenElement.click();
hiddenElement.remove();
}}
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">
{$_('sponsoring-quittungs-liste_herunterladen')}
</button>
{/if}
</span> </span>
<DonorsOverview bind:current_donors /> <DonorsOverview bind:current_donors />
</section> </section>

View File

@@ -1,5 +1,5 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { DonationService, DonorService } from "@odit/lfk-client-js"; import { DonationService, DonorService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import DonorsEmptyState from "./DonorsEmptyState.svelte"; import DonorsEmptyState from "./DonorsEmptyState.svelte";
@@ -77,6 +77,11 @@
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('total-donation-amount')} {$_('total-donation-amount')}
</th> </th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('total-paid-amount')}
</th>
<th scope="col" class="relative px-6 py-3"> <th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span> <span class="sr-only">{$_('action')}</span>
</th> </th>
@@ -127,7 +132,7 @@
<a <a
href="../donations/{d.id}" href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname}
{d.runner.middlename} {d.runner.middlename || ''}
{d.runner.lastname}</a> {d.runner.lastname}</a>
{:else} {:else}
<a <a
@@ -145,6 +150,11 @@
.toFixed(2) .toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })} .toLocaleString('de-DE', { valute: 'EUR' })}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap">
{(donor.paidDonationAmount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}
</td>
{#if active_deletes[donor.id] === true} {#if active_deletes[donor.id] === true}
<td <td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">

View File

@@ -32,8 +32,12 @@
target="_blank" target="_blank"
rel="noopener, noreferrer" rel="noopener, noreferrer"
href="https://git.odit.services/lfk/frontend/src/tag/{releaseinfo}">{releaseinfo}</a> href="https://git.odit.services/lfk/frontend/src/tag/{releaseinfo}">{releaseinfo}</a>
- -
<a class="underline" href="https://docs.lauf-fuer-kaya.de" target="_blank">{$_('documentation')}</a> <a
rel="noopener, noreferrer"
class="underline"
href="https://docs.lauf-fuer-kaya.de"
target="_blank">{$_('documentation')}</a>
- -
<a class="underline" href="/privacy">{$_('privacy')}</a> <a class="underline" href="/privacy">{$_('privacy')}</a>
- -

View File

@@ -19,7 +19,7 @@
) )
.then((resp) => { .then((resp) => {
Toastify({ Toastify({
text: "Organization deleted", text: $_('organization-deleted'),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();

View File

@@ -11,6 +11,9 @@
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select"; import Select from "svelte-select";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import { tick } from "svelte";
$: delete_triggered = false; $: delete_triggered = false;
$: address_valid_or_none = $: address_valid_or_none =
(isAddress1Valid && iszipcodevalid && iscityvalid) || (isAddress1Valid && iszipcodevalid && iscityvalid) ||
@@ -19,6 +22,9 @@
let original = ""; let original = "";
let original_object = {}; let original_object = {};
let contacts = []; let contacts = [];
let valueCopy = null;
let areaDom;
let copied = false;
export let params; export let params;
$: editable = {}; $: editable = {};
$: contact = {}; $: contact = {};
@@ -28,7 +34,10 @@
$: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0; $: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0;
$: iscityvalid = editable.address?.city?.trim().length !== 0; $: iscityvalid = editable.address?.city?.trim().length !== 0;
$: sponsoring_contracts_show = true; $: sponsoring_contracts_show = true;
$: cards_show = true;
$: certificates_show = true;
$: generate_orgs = [original_object]; $: generate_orgs = [original_object];
$: registrationLink = `${config.baseurl}/selfservice/register/${editable.registrationKey}`;
const getContactLabel = (option) => const getContactLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne( const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne(
@@ -96,6 +105,7 @@
postdata postdata
) )
.then((resp) => { .then((resp) => {
editable.registrationKey = resp.registrationKey;
original_object = Object.assign({}, editable); original_object = Object.assign({}, editable);
original = JSON.stringify(original_object); original = JSON.stringify(original_object);
Toastify({ Toastify({
@@ -108,9 +118,46 @@
} else { } else {
} }
} }
async function copy() {
if(!editable.registrationKey){
Toastify({
text: $_('you-have-to-save-your-changes-to-generate-a-link'),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
return;
}
valueCopy = registrationLink;
await tick();
areaDom.focus();
areaDom.select();
try {
const successful = document.execCommand("copy");
if (!successful) {
throw new Error();
}
Toastify({
text: $_("copied-link-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;
}
export let import_modal_open = false; export let import_modal_open = false;
</script> </script>
{#if valueCopy != null}<textarea bind:this={areaDom}>{valueCopy}</textarea>{/if}
<ImportRunnerModal <ImportRunnerModal
on:cancelDelete={(event) => { on:cancelDelete={(event) => {
import_modal_open = false; import_modal_open = false;
@@ -130,6 +177,8 @@
<GenerateSponsoringContracts <GenerateSponsoringContracts
bind:sponsoring_contracts_show bind:sponsoring_contracts_show
bind:generate_orgs /> bind:generate_orgs />
<GenerateRunnerCards bind:cards_show bind:generate_orgs />
<GenerateRunnerCertificates bind:certificates_show bind:generate_orgs />
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
<button <button
on:click={() => { on:click={() => {
@@ -140,7 +189,7 @@
{$_('import-runners')} {$_('import-runners')}
</button> </button>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
{#if delete_triggered} {#if delete_triggered}
<button <button
on:click={deleteOrganization} on:click={deleteOrganization}
@@ -272,99 +321,151 @@
on:select={(selectedValue) => (editable.contact = selectedValue.detail.value)} on:select={(selectedValue) => (editable.contact = selectedValue.detail.value)}
on:clear={() => (editable.contact = null)} /> on:clear={() => (editable.contact = null)} />
</div> </div>
<!-- --> <div>
<div class="flex items-start mt-2"> <div class="flex items-start mt-2">
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input <input
bind:checked={editable.address_checked} bind:checked={editable.registrationEnabled}
id="comments" id="comments"
name="comments" name="comments"
type="checkbox" type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label
for="comments"
class="font-medium text-gray-700">{$_('selfservice-registration')}</label>
</div>
</div> </div>
<div class="ml-3 text-sm"> <div>
<label {#if editable.registrationEnabled}
for="comments" <div class="text-sm w-full">
class="font-medium text-gray-700">{$_('address')}</label> <div on:click={copy} class="inline-flex w-full">
<p
name="token"
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">
{#if editable.registrationKey}
{registrationLink}
{:else}
{$_('you-have-to-save-your-changes-to-generate-a-link')}
{/if}
</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>
{#if editable.registrationKey}
<p class="text-gray-500 text-xs">
{$_('click-to-copy-the-link-into-your-clipboard')}
</p>
{/if}
</div>
{/if}
<!-- -->
<div>
<div class="flex items-start mt-2">
<div class="flex items-center h-5">
<input
bind:checked={editable.address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label
for="comments"
class="font-medium text-gray-700">{$_('address')}</label>
</div>
</div>
</div>
{#if editable.address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700">{$_('address')}</label>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('address-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label>
<input
autocomplete="off"
placeholder={$_('apartment-suite-etc')}
bind:value={editable.address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label>
<input
autocomplete="off"
placeholder={$_('zip-postal-code')}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-zipcode-postal-code-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700">{$_('city')}</label>
<input
autocomplete="off"
placeholder={$_('city')}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-city-is-required')}
</span>
{/if}
</div>
{/if}
</div> </div>
</div> </div>
{#if editable.address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700">{$_('address')}</label>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('address-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label>
<input
autocomplete="off"
placeholder={$_('apartment-suite-etc')}
bind:value={editable.address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label>
<input
autocomplete="off"
placeholder={$_('zip-postal-code')}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-zipcode-postal-code-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700">{$_('city')}</label>
<input
autocomplete="off"
placeholder={$_('city')}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-city-is-required')}
</span>
{/if}
</div>
{/if}
</section> </section>
{:else} {:else}
{#await promise} {#await promise}

View File

@@ -1,207 +1,219 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
let modal_open = false; let modal_open = false;
let delete_org = {}; let delete_org = {};
import { RunnerOrganizationService } from "@odit/lfk-client-js"; import { RunnerOrganizationService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import OrgsEmptyState from "./OrgsEmptyState.svelte"; import OrgsEmptyState from "./OrgsEmptyState.svelte";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte"; import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
$: searchvalue = ""; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: active_deletes = []; import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
$: sponsoring_contracts_show = current_organizations.some((r) => r.is_selected === true); $: searchvalue = "";
$: generate_orgs = current_organizations.filter((r) => r.is_selected === true); $: active_deletes = [];
export let current_organizations = []; $: sponsoring_contracts_show = current_organizations.some((r) => r.is_selected === true);
$: cards_show = current_organizations.some((r) => r.is_selected === true);
const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then( $: generate_orgs = current_organizations.filter((r) => r.is_selected === true);
(val) => { $: certificates_show = current_organizations.some(
current_organizations = val; (r) => r.is_selected === true
} );
); export let current_organizations = [];
</script>
const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
<ConfirmOrgDeletion (val) => {
on:cancelDelete={(event) => { current_organizations = val;
modal_open = false; }
active_deletes[event.detail.id] = false; );
}} </script>
bind:modal_open
bind:delete_org /> <ConfirmOrgDeletion
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')} on:cancelDelete={(event) => {
{#await promise} modal_open = false;
<div active_deletes[event.detail.id] = false;
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" }}
role="alert"> bind:modal_open
<p class="font-bold">{$_('organizations-are-being-loaded')}</p> bind:delete_org />
<p class="text-sm">{$_('this-might-take-a-moment')}</p> {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
</div> {#await promise}
{:then} <div
{#if current_organizations.length === 0} class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
<OrgsEmptyState /> role="alert">
{:else} <p class="font-bold">{$_('organizations-are-being-loaded')}</p>
<input <p class="text-sm">{$_('this-might-take-a-moment')}</p>
type="search" </div>
bind:value={searchvalue} {:then}
placeholder={$_('datatable.search')} {#if current_organizations.length === 0}
aria-label={$_('datatable.search')} <OrgsEmptyState />
class="gridjs-input gridjs-search-input mb-4" /> {:else}
<div class="h-12"> <input
<GenerateSponsoringContracts type="search"
bind:sponsoring_contracts_show bind:value={searchvalue}
bind:generate_orgs /> placeholder={$_('datatable.search')}
</div> aria-label={$_('datatable.search')}
<div class="gridjs-input gridjs-search-input mb-4" />
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> <div class="h-12">
<table class="divide-y divide-gray-200 w-full"> <GenerateSponsoringContracts
<thead class="bg-gray-50"> bind:sponsoring_contracts_show
<tr> bind:generate_orgs />
<th <GenerateRunnerCards
scope="col" bind:cards_show
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> bind:generate_orgs />
<span <GenerateRunnerCertificates
on:click={() => { bind:certificates_show
const newstate = !current_organizations.some((r) => r.is_selected === true); bind:generate_orgs />
current_organizations = current_organizations.map((r) => { </div>
r.is_selected = newstate; <div
return r; 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">
class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)} <tr>
{$_('deselect-all')} <th
{:else}{$_('select-all')}{/if} scope="col"
</span> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th> <span
<th on:click={() => {
scope="col" const newstate = !current_organizations.some((r) => r.is_selected === true);
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> current_organizations = current_organizations.map((r) => {
{$_('name')} r.is_selected = newstate;
</th> return r;
<th });
scope="col" }}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)}
{$_('address')} {$_('deselect-all')}
</th> {:else}{$_('select-all')}{/if}
<th </span>
scope="col" </th>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th
{$_('contact')} scope="col"
</th> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th scope="col" class="relative px-6 py-3"> {$_('name')}
<span class="sr-only">{$_('action')}</span> </th>
</th> <th
</tr> scope="col"
</thead> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<tbody class="divide-y divide-gray-200"> {$_('address')}
{#each current_organizations as o} </th>
{#if Object.values(o) <th
.toString() scope="col"
.toLowerCase() class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
.includes(searchvalue)} {$_('contact')}
<tr data-rowid="org_{o.id}"> </th>
<td class="px-6 py-4 whitespace-nowrap"> <th scope="col" class="relative px-6 py-3">
<input <span class="sr-only">{$_('action')}</span>
bind:checked={o.is_selected} </th>
type="checkbox" </tr>
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> </thead>
</td> <tbody class="divide-y divide-gray-200">
<td class="px-6 py-4 whitespace-nowrap"> {#each current_organizations as o}
<div class="flex items-center"> {#if Object.values(o)
<div class="ml-4"> .toString()
<div class="text-sm font-medium text-gray-900"> .toLowerCase()
{o.name} .includes(searchvalue)}
</div> <tr data-rowid="org_{o.id}">
</div> <td class="px-6 py-4 whitespace-nowrap">
</div> <input
</td> bind:checked={o.is_selected}
<td class="px-6 py-4 whitespace-nowrap"> type="checkbox"
<div class="flex items-center"> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
<div class="ml-4"> </td>
<div class="text-sm font-medium text-gray-900"> <td class="px-6 py-4 whitespace-nowrap">
{#if o.address.address1 !== null} <div class="flex items-center">
{o.address.address1}<br /> <div class="ml-4">
{o.address.address2 || ''}<br /> <div class="text-sm font-medium text-gray-900">
{o.address.postalcode} {o.name}
{o.address.city} </div>
{o.address.country} </div>
{/if} </div>
</div> </td>
</div> <td class="px-6 py-4 whitespace-nowrap">
</div> <div class="flex items-center">
</td> <div class="ml-4">
<td class="px-6 py-4 whitespace-nowrap"> <div class="text-sm font-medium text-gray-900">
<div class="flex items-center"> {#if o.address.address1 !== null}
<div class="ml-4"> {o.address.address1}<br />
<div class="text-sm font-medium text-gray-900"> {o.address.address2 || ''}<br />
{#if o.contact} {o.address.postalcode}
<a {o.address.city}
href="../contacts/{o.contact.id}" {o.address.country}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname} {/if}
{o.contact.middlename || ''} </div>
{o.contact.lastname}</a> </div>
{:else}{$_('no-contact-specified')}{/if} </div>
</div> </td>
</div> <td class="px-6 py-4 whitespace-nowrap">
</div> <div class="flex items-center">
</td> <div class="ml-4">
{#if active_deletes[o.id] === true} <div class="text-sm font-medium text-gray-900">
<td {#if o.contact}
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <a
<button href="../contacts/{o.contact.id}"
on:click={() => { class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname}
active_deletes[o.id] = false; {o.contact.middlename || ''}
}} {o.contact.lastname}</a>
tabindex="0" {:else}{$_('no-contact-specified')}{/if}
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> </div>
<button </div>
on:click={() => { </div>
RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false) </td>
.then((resp) => { {#if active_deletes[o.id] === true}
current_organizations = current_organizations.filter((obj) => obj.id !== o.id); <td
Toastify({ class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
text: 'Organization deleted', <button
duration: 500, on:click={() => {
backgroundColor: active_deletes[o.id] = false;
'linear-gradient(to right, #00b09b, #96c93d)', }}
}).showToast(); tabindex="0"
}) class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
.catch((err) => { <button
modal_open = true; on:click={() => {
delete_org = o; RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false)
}); .then((resp) => {
}} current_organizations = current_organizations.filter((obj) => obj.id !== o.id);
tabindex="0" Toastify({
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> text: 'Organization deleted',
</td> duration: 500,
{:else} backgroundColor:
<td 'linear-gradient(to right, #00b09b, #96c93d)',
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> }).showToast();
<a })
href="./{o.id}" .catch((err) => {
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> modal_open = true;
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')} delete_org = o;
<button });
on:click={() => { }}
active_deletes[o.id] = true; tabindex="0"
}} class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
tabindex="0" </td>
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> {:else}
{/if} <td
</td> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
{/if} <a
</tr> href="./{o.id}"
{/if} class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{/each} {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')}
</tbody> <button
</table> on:click={() => {
</div> active_deletes[o.id] = true;
{/if} }}
{:catch error} tabindex="0"
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
<span class="inline-block align-middle mr-8"> {/if}
<b class="capitalize">{$_('general_promise_error')}</b> </td>
{error} {/if}
</span> </tr>
</div> {/if}
{/await} {/each}
{/if} </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,403 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
RunnerCardService,
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let cards_show = false;
export let generate_cards = [];
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: cards_dropdown_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "cards:dropdown" &&
e.target.parentNode?.parentNode?.id != "cards:dropdown:menu"
) {
cards_dropdown_open = false;
}
});
function generateRunnerCards(locale) {
cards_dropdown_open = false;
if (generate_orgs.length > 0) {
generateOrgCards(locale);
} else if (generate_teams.length > 0) {
generateTeamCards(locale);
} else if (generate_runners.length > 0) {
generateRunnersCards(locale);
} else {
generateCards(locale);
}
}
function generateCards(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(generate_cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('runnercards')}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
console.error(err);
});
}
async function generateRunnersCards(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let cards = [];
for (let runner of generate_runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
if(generate_runners.length == 1){
a.download = `${$_('runnercards')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`;
}
else{
a.download = `Runnercards-${locale}.pdf`;
}
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
}
async function generateTeamCards(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
for (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let cards = [];
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('runnercards')}_${t.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_teams.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
async function generateOrgCards(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let count = 0;
let count_orgs =0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(o.id, true)
let cards = [];
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
await fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('runnercards')}_${o.name}_direct-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
console.log("here")
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let cards = [];
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
await fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('runnercards')}_${o.name}_${t.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
}
</script>
{#if cards_show}
<div id="cards:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
cards_dropdown_open = !cards_dropdown_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-runnercards')}
<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 cards_dropdown_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="cards: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={() => {
generateRunnerCards('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"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateRunnerCards('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"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}

View File

@@ -0,0 +1,331 @@
<script>
import { _ } from "svelte-i18n";
import {
DonationService,
RunnerTeamService,
RunnerOrganizationService
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let certificates_show = false;
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: certificates_dropdown_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "certificates:dropdown" &&
e.target.parentNode?.parentNode?.id != "certificates:dropdown:menu"
) {
certificates_dropdown_open = false;
}
});
function generateCertificates(locale) {
certificates_dropdown_open = false;
if (generate_orgs.length > 0) {
generateOrgCertificates(locale);
} else if (generate_teams.length > 0) {
generateTeamCertificates(locale);
} else {
generateRunnerCertificates(locale);
}
}
async function generateRunnerCertificates(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
const current_donations = (await DonationService.donationControllerGetAll()) || [];
let certificateRunners = [];
for (let runner of generate_runners) {
runner.distanceDonations = current_donations.find((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
if(generate_runners.length == 1){
a.download = `${$_('certificates')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`;
}
else{
a.download = `${$_('certificates')}-${locale}.pdf`;
}
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
}
async function generateTeamCertificates(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
const current_donations = (await DonationService.donationControllerGetAll()) || [];
for (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations = current_donations.find((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('certificates')}_${t.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_teams.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
async function generateOrgCertificates(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
const current_donations = (await DonationService.donationControllerGetAll()) || [];
let count = 0;
let count_orgs =0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(o.id, true)
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations = current_donations.find((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('certificates')}_${o.name}_direct-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
console.log("here")
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations = current_donations.find((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('certificates')}_${o.name}_${t.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
}
</script>
{#if certificates_show}
<div id="certificates:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
certificates_dropdown_open = !certificates_dropdown_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-runner-certificates')}
<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 certificates_dropdown_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="certificates: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={() => {
generateCertificates('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"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateCertificates('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"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}

View File

@@ -1,252 +1,306 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { RunnerOrganizationService, RunnerTeamService } from "@odit/lfk-client-js"; import {
import Toastify from "toastify-js"; RunnerOrganizationService,
export let sponsoring_contracts_show = false; RunnerTeamService,
export let generate_runners = []; } from "@odit/lfk-client-js";
export let generate_orgs = []; import Toastify from "toastify-js";
export let generate_teams = []; export let sponsoring_contracts_show = false;
$: sponsoring_contracts_download_open = false; export let generate_runners = [];
document.addEventListener("click", function (e) { export let generate_orgs = [];
if ( export let generate_teams = [];
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && $: sponsoring_contracts_download_open = false;
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" document.addEventListener("click", function (e) {
) { if (
sponsoring_contracts_download_open = false; e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
} e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
}); ) {
sponsoring_contracts_download_open = false;
function generateSponsoringContract(locale) { }
sponsoring_contracts_download_open = false; });
if (generate_orgs.length > 0) { function generateSponsoringContract(locale) {
generateOrgContracts(locale); sponsoring_contracts_download_open = false;
} else if (generate_teams.length > 0) {
generateTeamContracts(locale); if (generate_orgs.length > 0) {
} else { generateOrgContracts(locale);
generateRunnerContracts(locale); } else if (generate_teams.length > 0) {
} generateTeamContracts(locale);
} } else {
generateRunnerContracts(locale);
async function generateTeamContracts(locale) { }
const toast = Toastify({ }
text: $_("generating-pdfs"),
duration: -1, async function generateTeamContracts(locale) {
}).showToast(); const toast = Toastify({
let count = 0; text: $_("generating-pdfs"),
for await (const t of generate_teams) { duration: -1,
count++; }).showToast();
const runners = await RunnerTeamService.runnerTeamControllerGetRunners( let count = 0;
t.id for (const t of generate_teams) {
); count++;
fetch( const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, t.id
{ );
method: "POST", fetch(
headers: { `${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
"Content-Type": "application/json", {
}, method: "POST",
body: JSON.stringify(runners), headers: {
} "Content-Type": "application/json",
) },
.then((response) => { body: JSON.stringify(runners),
if (response.status != "200") { }
toast.hideToast(); )
Toastify({ .then((response) => {
text: $_("pdf-generation-failed"), if (response.status != "200") {
duration: 3500, toast.hideToast();
backgroundColor: Toastify({
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", text: $_("pdf-generation-failed"),
}).showToast(); duration: 3500,
} else { backgroundColor:
return response.blob(); "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
} }).showToast();
}) } else {
.then((blob) => { return response.blob();
const url = window.URL.createObjectURL(blob); }
let a = document.createElement("a"); })
a.href = url; .then((blob) => {
a.download = "Sponsorings_" + t.name + ".pdf"; const url = window.URL.createObjectURL(blob);
document.body.appendChild(a); let a = document.createElement("a");
a.click(); a.href = url;
a.remove(); a.download = `${$_('sponsorings')}_${t.name}-${locale}.pdf`;
if (count === generate_teams.length) { document.body.appendChild(a);
toast.hideToast(); a.click();
Toastify({ a.remove();
text: $_("pdfs-successfully-generated"), if (count === generate_teams.length) {
duration: 3500, toast.hideToast();
backgroundColor: Toastify({
"linear-gradient(to right, #00b09b, #96c93d)", text: $_("pdfs-successfully-generated"),
}).showToast(); duration: 3500,
} backgroundColor:
}) "linear-gradient(to right, #00b09b, #96c93d)",
.catch((err) => {}); }).showToast();
} }
} })
.catch((err) => {});
async function generateOrgContracts(locale) { }
const toast = Toastify({ }
text: $_("generating-pdf"),
duration: -1, async function generateOrgContracts(locale) {
}).showToast(); const toast = Toastify({
let count = 0; text: $_("generating-pdf"),
for await (const o of generate_orgs) { duration: -1,
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners( }).showToast();
o.id let count_orgs =0;
); for (const o of generate_orgs) {
fetch( count_orgs++;
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, let count = 0;
{ let runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(o.id, true)
method: "POST", await fetch(
headers: { `${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
"Content-Type": "application/json", {
}, method: "POST",
body: JSON.stringify(runners), headers: {
} "Content-Type": "application/json",
) },
.then((response) => { body: JSON.stringify(runners),
if (response.status != "200") { }
toast.hideToast(); )
Toastify({ .then((response) => {
text: $_("pdf-generation-failed"), if (response.status != "200") {
duration: 3500, toast.hideToast();
backgroundColor: Toastify({
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", text: $_("pdf-generation-failed"),
}).showToast(); duration: 3500,
} else { backgroundColor:
return response.blob(); "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
} }).showToast();
}) } else {
.then((blob) => { return response.blob();
count++; }
const url = window.URL.createObjectURL(blob); })
let a = document.createElement("a"); .then((blob) => {
a.href = url; const url = window.URL.createObjectURL(blob);
a.download = "Sponsorings_" + o.name + ".pdf"; let a = document.createElement("a");
document.body.appendChild(a); a.href = url;
a.click(); a.download = `${$_('sponsorings')}_${o.name}_direct-${locale}.pdf`;
a.remove(); document.body.appendChild(a);
if (count === generate_orgs.length) { a.click();
toast.hideToast(); a.remove();
Toastify({ if (count === o.teams.length && count_orgs === generate_orgs.length) {
text: $_("pdfs-successfully-generated"), toast.hideToast();
duration: 3500, console.log("here")
backgroundColor: Toastify({
"linear-gradient(to right, #00b09b, #96c93d)", text: $_("pdfs-successfully-generated"),
}).showToast(); duration: 3500,
} backgroundColor:
}) "linear-gradient(to right, #00b09b, #96c93d)",
.catch((err) => {}); }).showToast();
} }
} })
.catch((err) => {});
function generateRunnerContracts(locale) { for (const t of o.teams) {
const toast = Toastify({ count++;
text: $_("generating-pdf"), let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
duration: -1, t.id
}).showToast(); );
fetch( await fetch(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, `${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(generate_runners), body: JSON.stringify(runners),
} }
) )
.then((response) => { .then((response) => {
if (response.status != "200") { if (response.status != "200") {
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdf-generation-failed"), text: $_("pdf-generation-failed"),
duration: 3500, duration: 3500,
backgroundColor: backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast(); }).showToast();
} else { } else {
return response.blob(); return response.blob();
} }
}) })
.then((blob) => { .then((blob) => {
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
let a = document.createElement("a"); let a = document.createElement("a");
a.href = url; a.href = url;
a.download = "Sponsoring.pdf"; a.download = `${$_('sponsorings')}_${o.name}_${t.name}-${locale}.pdf`;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
a.remove(); a.remove();
toast.hideToast(); if (count === o.teams.length && count_orgs === generate_orgs.length) {
Toastify({ toast.hideToast();
text: $_("pdf-successfully-generated"), Toastify({
duration: 3500, text: $_("pdfs-successfully-generated"),
backgroundColor: duration: 3500,
"linear-gradient(to right, #00b09b, #96c93d)", backgroundColor:
}).showToast(); "linear-gradient(to right, #00b09b, #96c93d)",
}) }).showToast();
.catch((err) => { }
console.error(err); })
}); .catch((err) => {});
} }
</script> }
}
{#if sponsoring_contracts_show}
<div id="sponsoring:dropdown" class="relative inline-block"> function generateRunnerContracts(locale) {
<div> const toast = Toastify({
<button text: $_("generating-pdf"),
on:click={() => { duration: -1,
sponsoring_contracts_download_open = !sponsoring_contracts_download_open; }).showToast();
}} fetch(
type="button" `${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
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" method: "POST",
aria-haspopup="true" headers: {
aria-expanded="true"> "Content-Type": "application/json",
{$_('generate-sponsoring-contracts')} },
<svg body: JSON.stringify(generate_runners),
xmlns="http://www.w3.org/2000/svg" }
width="24" )
height="24" .then((response) => {
viewBox="0 0 24 24" if (response.status != "200") {
class="-mr-1 ml-2 h-5 w-5"><path toast.hideToast();
fill="none" Toastify({
d="M0 0h24v24H0z" /> text: $_("pdf-generation-failed"),
<path duration: 3500,
fill="currentColor" backgroundColor:
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> "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
</button> }).showToast();
</div> } else {
{#if sponsoring_contracts_download_open} return response.blob();
<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"> .then((blob) => {
<div const url = window.URL.createObjectURL(blob);
class="py-1" let a = document.createElement("a");
role="menu" a.href = url;
aria-orientation="vertical" if(generate_runners.length == 1){
aria-labelledby="options-menu"> a.download = `${$_('sponsorings')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`;
<span }
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> a.download = `${$_('sponsorings')}-${locale}.pdf`;
<button document.body.appendChild(a);
on:click={() => { a.click();
generateSponsoringContract('de'); a.remove();
}} toast.hideToast();
type="submit" Toastify({
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" text: $_("pdf-successfully-generated"),
role="menuitem"> duration: 3500,
{$_('german')} backgroundColor:
</button> "linear-gradient(to right, #00b09b, #96c93d)",
<button }).showToast();
on:click={() => { })
generateSponsoringContract('en'); .catch((err) => {
}} console.error(err);
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" }
role="menuitem"> </script>
{$_('english')}
</button> {#if sponsoring_contracts_show}
</div> <div id="sponsoring:dropdown" class="relative inline-block">
</div> <div>
{/if} <button
</div> on:click={() => {
{/if} 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"
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"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}

View File

@@ -34,7 +34,7 @@
document.onkeydown = (e) => { document.onkeydown = (e) => {
e = e || window.event; e = e || window.event;
if (e.key === "Escape") { if (e.key === "Escape") {
import_modal_open = false; cancelModal();
} }
if (e.keyCode === 13) { if (e.keyCode === 13) {
// //
@@ -202,7 +202,7 @@
toast.hideToast(); toast.hideToast();
recent_processed = true; recent_processed = true;
Toastify({ Toastify({
text: "Import finished", text: $_('import-finished'),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
@@ -281,6 +281,16 @@
bind:files bind:files
type="file" /> type="file" />
</div> </div>
<div class="overflow-hidden relative mt-4 mb-4">
<button
on:click={() => {
cancelModal();
}}
type="button"
class="w-full 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 md:ml-40 mr-0 sm:ml-0 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
{/if} {/if}
{#if json_output.length > 0} {#if json_output.length > 0}
{#if opened_from === 'OrgOverview'} {#if opened_from === 'OrgOverview'}

View File

@@ -1,293 +1,302 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelteContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import store from "../../store"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import { import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
RunnerService, import store from "../../store";
RunnerTeamService, import {
RunnerOrganizationService, RunnerService,
} from "@odit/lfk-client-js"; RunnerTeamService,
import Toastify from "toastify-js"; RunnerOrganizationService,
import PromiseError from "../base/PromiseError.svelte"; } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail"; import Toastify from "toastify-js";
import Select from "svelte-select"; import PromiseError from "../base/PromiseError.svelte";
let data_loaded = false; import isEmail from "validator/es/lib/isEmail";
export let params; import Select from "svelte-select";
const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid); let data_loaded = false;
$: delete_triggered = false; export let params;
$: sponsoring_contracts_download_open = false; const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid);
$: original_data_pdf = {}; $: delete_triggered = false;
$: original_data = {}; $: original_data_pdf = {};
$: editable = {}; $: original_data = {};
$: group = {}; $: editable = {};
$: changes_performed = !( $: group = {};
JSON.stringify(original_data) == JSON.stringify(editable) $: changes_performed = !(
); JSON.stringify(original_data) == JSON.stringify(editable)
$: isEmailValid = );
(editable.email || "") === "" || $: isEmailValid =
(editable.email && isEmail(editable.email || "")); (editable.email || "") === "" ||
$: isFirstnameValid = editable.firstname !== ""; (editable.email && isEmail(editable.email || ""));
$: isLastnameValid = editable.lastname !== ""; $: isFirstnameValid = editable.firstname !== "";
$: save_enabled = $: isLastnameValid = editable.lastname !== "";
changes_performed && $: save_enabled =
isFirstnameValid && changes_performed &&
isLastnameValid && isFirstnameValid &&
isEmailValid && isLastnameValid &&
editable.group != null; isEmailValid &&
$: sponsoring_contracts_show = true; editable.group != null;
$: generate_runners = [original_data_pdf]; $: sponsoring_contracts_show = true;
runner_promise.then((data) => { $: cards_show = true;
data_loaded = true; $: certificates_show = true;
original_data_pdf = Object.assign(original_data_pdf, data); $: generate_runners = [original_data_pdf];
data.group = data.group.id; runner_promise.then((data) => {
original_data = Object.assign(original_data, data); data_loaded = true;
editable = Object.assign(editable, original_data); original_data_pdf = Object.assign(original_data_pdf, data);
data.group = data.group.id;
RunnerOrganizationService.runnerOrganizationControllerGetAll().then( original_data = Object.assign(original_data, data);
(val) => { editable = Object.assign(editable, original_data);
const orgs = val.map((r) => {
return { label: r.name, value: r }; RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
}); (val) => {
groups = groups.concat(orgs); const orgs = val.map((r) => {
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { return { label: r.name, value: r };
const teams = val.map((r) => { });
return { label: `${r.parentGroup.name} > ${r.name}`, value: r }; groups = groups.concat(orgs);
}); RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
groups = groups.concat(teams); const teams = val.map((r) => {
group = groups.find((g) => g.value.id == editable.group); return { label: `${r.parentGroup.name} > ${r.name}`, value: r };
}); });
} groups = groups.concat(teams);
); group = groups.find((g) => g.value.id == editable.group);
}); });
let groups = []; }
function submit() { );
if (data_loaded === true && save_enabled) { });
Toastify({ let groups = [];
text: $_("updating-runner"), function submit() {
duration: 2500, if (data_loaded === true && save_enabled) {
}).showToast(); Toastify({
let postdata = {}; text: $_("updating-runner"),
postdata = Object.assign(postdata, editable); duration: 2500,
RunnerService.runnerControllerPut(original_data.id, postdata) }).showToast();
.then((resp) => { let postdata = {};
Object.assign(original_data, editable); postdata = Object.assign(postdata, editable);
original_data = original_data; RunnerService.runnerControllerPut(original_data.id, postdata)
Toastify({ .then((resp) => {
text: $_("runner-updated"), Object.assign(original_data, editable);
duration: 2500, original_data = original_data;
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", Toastify({
}).showToast(); text: $_("runner-updated"),
}) duration: 2500,
.catch((err) => {}); backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
} else { }).showToast();
} })
} .catch((err) => {});
function deleteRunner() { } else {
RunnerService.runnerControllerRemove(original_data.id, true) }
.then((resp) => { }
location.replace("./"); function deleteRunner() {
}) RunnerService.runnerControllerRemove(original_data.id, true)
.catch((err) => {}); .then((resp) => {
} location.replace("./");
</script> })
.catch((err) => {});
{#await runner_promise} }
{$_('loading-runners')} </script>
{:then}
<section class="container p-5 select-none"> {#await runner_promise}
<div class="flex flex-row mb-4"> {$_('loading-runners')}
<div class="w-full"> {:then}
<nav class="w-full flex"> <section class="container p-5 select-none">
<ol class="list-none flex flex-row items-center justify-start"> <div class="flex flex-row mb-4">
<li class="flex items-center"> <div class="w-full">
<svg <nav class="w-full flex">
xmlns="http://www.w3.org/2000/svg" <ol class="list-none flex flex-row items-center justify-start">
viewBox="0 0 24 24" <li class="flex items-center">
class="flex-shrink-0 w-5 h-5 mr-2" <svg
fill="currentColor" xmlns="http://www.w3.org/2000/svg"
width="24" viewBox="0 0 24 24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> class="flex-shrink-0 w-5 h-5 mr-2"
<path fill="currentColor"
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg> width="24"
</li> height="24"><path fill="none" d="M0 0h24v24H0z" />
<li class="flex items-center"> <path
<a class="mr-2" href="./">{$_('runners')}</a><svg d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
stroke="currentColor" </li>
fill="none" <li class="flex items-center">
stroke-width="2" <a class="mr-2" href="./">{$_('runners')}</a><svg
viewBox="0 0 24 24" stroke="currentColor"
stroke-linecap="round" fill="none"
stroke-linejoin="round" stroke-width="2"
class="h-3 w-3 mr-2 stroke-current" viewBox="0 0 24 24"
height="1em" stroke-linecap="round"
width="1em" stroke-linejoin="round"
xmlns="http://www.w3.org/2000/svg"><line class="h-3 w-3 mr-2 stroke-current"
x1="5" height="1em"
y1="12" width="1em"
x2="19" xmlns="http://www.w3.org/2000/svg"><line
y2="12" /> x1="5"
<polyline points="12 5 19 12 12 19" /></svg> y1="12"
</li> x2="19"
<li class="flex items-center"> y2="12" />
<span class="mr-2">{original_data.firstname} <polyline points="12 5 19 12 12 19" /></svg>
{original_data.middlename || ''} </li>
{original_data.lastname}</span> <li class="flex items-center">
</li> <span class="mr-2">{original_data.firstname}
</ol> {original_data.middlename || ''}
</nav> {original_data.lastname}</span>
</div> </li>
</div> </ol>
<div class="mb-8 text-3xl font-extrabold leading-tight"> </nav>
{original_data.firstname} </div>
{original_data.middlename || ''} </div>
{original_data.lastname} <div class="mb-8 text-3xl font-extrabold leading-tight">
<span data-id="runner_actions_${editable.id}"> {original_data.firstname}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')} {original_data.middlename || ''}
{#if delete_triggered} {original_data.lastname}
<button <span data-id="runner_actions_${editable.id}">
on:click={deleteRunner} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
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> {#if delete_triggered}
<button <button
on:click={() => { on:click={deleteRunner}
delete_triggered = !delete_triggered; 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
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> on:click={() => {
{/if} delete_triggered = !delete_triggered;
<GenerateSponsoringContracts }}
bind:sponsoring_contracts_show 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>
bind:generate_runners /> {/if}
{#if !delete_triggered} <GenerateSponsoringContracts
<button bind:sponsoring_contracts_show
on:click={() => { bind:generate_runners />
delete_triggered = true; <GenerateRunnerCards
}} bind:cards_show
type="button" bind:generate_runners />
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-runner')}</button> <GenerateRunnerCertificates
{/if} bind:certificates_show
{/if} bind:generate_runners />
{#if !delete_triggered} {#if !delete_triggered}
<button <button
disabled={!save_enabled} on:click={() => {
class:opacity-50={!save_enabled} delete_triggered = true;
type="button" }}
on:click={submit} type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> 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-runner')}</button>
{/if} {/if}
</span> {/if}
</div> {#if !delete_triggered}
<!-- --> <button
<div class="text-sm w-full"> disabled={!save_enabled}
<label class:opacity-50={!save_enabled}
for="firstname" type="button"
class="font-medium text-gray-700">{$_('first-name')}</label> on:click={submit}
<input 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>
autocomplete="off" {/if}
placeholder={$_('first-name')} </span>
type="text" </div>
class:border-red-500={!isFirstnameValid} <!-- -->
class:focus:border-red-500={!isFirstnameValid} <div class="text-sm w-full">
class:focus:ring-red-500={!isFirstnameValid} <label
bind:value={editable.firstname} for="firstname"
name="firstname" class="font-medium text-gray-700">{$_('first-name')}</label>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> <input
{#if !isFirstnameValid} autocomplete="off"
<span placeholder={$_('first-name')}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> type="text"
{$_('first-name-is-required')} class:border-red-500={!isFirstnameValid}
</span> class:focus:border-red-500={!isFirstnameValid}
{/if} class:focus:ring-red-500={!isFirstnameValid}
</div> bind:value={editable.firstname}
<div class="text-sm w-full"> name="firstname"
<label class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
for="middlename" {#if !isFirstnameValid}
class="font-medium text-gray-700">{$_('middle-name')}</label> <span
<input class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
autocomplete="off" {$_('first-name-is-required')}
placeholder={$_('middle-name')} </span>
type="text" {/if}
bind:value={editable.middlename} </div>
name="middlename" <div class="text-sm w-full">
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" /> <label
</div> for="middlename"
<div class="text-sm w-full"> class="font-medium text-gray-700">{$_('middle-name')}</label>
<label <input
for="lastname" autocomplete="off"
class="font-medium text-gray-700">{$_('last-name')}</label> placeholder={$_('middle-name')}
<input type="text"
autocomplete="off" bind:value={editable.middlename}
placeholder={$_('last-name')} name="middlename"
type="text" 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" />
bind:value={editable.lastname} </div>
class:border-red-500={!isLastnameValid} <div class="text-sm w-full">
class:focus:border-red-500={!isLastnameValid} <label
class:focus:ring-red-500={!isLastnameValid} for="lastname"
name="lastname" class="font-medium text-gray-700">{$_('last-name')}</label>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> <input
{#if !isLastnameValid} autocomplete="off"
<span placeholder={$_('last-name')}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> type="text"
{$_('last-name-is-required')} bind:value={editable.lastname}
</span> class:border-red-500={!isLastnameValid}
{/if} class:focus:border-red-500={!isLastnameValid}
</div> class:focus:ring-red-500={!isLastnameValid}
<div class="text-sm w-full"> name="lastname"
<label class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
for="email" {#if !isLastnameValid}
class="font-medium text-gray-700">{$_('e-mail-adress')}</label> <span
<input class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
autocomplete="off" {$_('last-name-is-required')}
placeholder={$_('e-mail-adress')} </span>
type="email" {/if}
bind:value={editable.email} </div>
class:border-red-500={!isEmailValid} <div class="text-sm w-full">
class:focus:border-red-500={!isEmailValid} <label
class:focus:ring-red-500={!isEmailValid} for="email"
name="email" class="font-medium text-gray-700">{$_('e-mail-adress')}</label>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> <input
{#if !isEmailValid} autocomplete="off"
<span placeholder={$_('e-mail-adress')}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> type="email"
{$_('valid-email-is-required')} bind:value={editable.email}
</span> class:border-red-500={!isEmailValid}
{/if} class:focus:border-red-500={!isEmailValid}
</div> class:focus:ring-red-500={!isEmailValid}
<div class="text-sm w-full"> name="email"
<label for="phone" class="font-medium text-gray-700">{$_('phone')}</label> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
<input {#if !isEmailValid}
autocomplete="off" <span
placeholder={$_('phone')} class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
type="tel" {$_('valid-email-is-required')}
bind:value={editable.phone} </span>
name="phone" {/if}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> </div>
</div> <div class="text-sm w-full">
<div class="text-sm w-full"> <label for="phone" class="font-medium text-gray-700">{$_('phone')}</label>
<span class="font-medium text-gray-700">{$_('group')}</span> <input
<Select autocomplete="off"
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" placeholder={$_('phone')}
itemFilter={(label, filterText, option) => label type="tel"
.toLowerCase() bind:value={editable.phone}
.includes( name="phone"
filterText.toLowerCase() 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.id.value </div>
.toString() <div class="text-sm w-full">
.startsWith(filterText.toLowerCase())} <span class="font-medium text-gray-700">{$_('group')}</span>
items={groups} <Select
showChevron={true} 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"
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')} itemFilter={(label, filterText, option) => label
noOptionsMessage={$_('no-organization-or-team-found')} .toLowerCase()
bind:selectedValue={group} .includes(
on:select={(selectedValue) => { filterText.toLowerCase()
editable.group = selectedValue.detail.value.id; ) || option.id.value
}} .toString()
on:clear={() => (editable.group = null)} /> .startsWith(filterText.toLowerCase())}
</div> items={groups}
<div class="text-sm w-full"> showChevron={true}
<span class="font-medium text-gray-700">{$_('distance')}</span> placeholder={$_('search-for-an-organization-or-team-by-name-or-id')}
<br /> noOptionsMessage={$_('no-organization-or-team-found')}
<span class="text-gray-700">{original_data.distance} km</span> bind:selectedValue={group}
</div> on:select={(selectedValue) => {
</section> editable.group = selectedValue.detail.value.id;
{:catch error} }}
<PromiseError {error} /> on:clear={() => (editable.group = null)} />
{/await} </div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('distance')}</span>
<br />
<span class="text-gray-700">{original_data.distance} km</span>
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}

View File

@@ -1,249 +1,263 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { import {
RunnerService, RunnerService,
RunnerTeamService, RunnerTeamService,
RunnerOrganizationService, RunnerOrganizationService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import RunnersEmptyState from "./RunnersEmptyState.svelte"; import RunnersEmptyState from "./RunnersEmptyState.svelte";
import Select from "svelte-select"; import Select from "svelte-select";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
$: searchvalue = ""; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: active_deletes = []; import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
export let current_runners = []; $: searchvalue = "";
const runners_promise = RunnerService.runnerControllerGetAll().then((val) => { $: active_deletes = [];
current_runners = val; export let current_runners = [];
}); const runners_promise = RunnerService.runnerControllerGetAll().then((val) => {
$: selectedFilter_teams = null; current_runners = val;
$: selectedFilter = null; });
$: filter__teams = selectedFilter_teams || []; $: selectedFilter_teams = null;
$: filter__orgs = selectedFilter || []; $: selectedFilter = null;
$: filterGroupIDs = filter__teams.concat(filter__orgs).map((i) => i.value); $: filter__teams = selectedFilter_teams || [];
$: sponsoring_contracts_show = current_runners.some( $: filter__orgs = selectedFilter || [];
(r) => r.is_selected === true $: filterGroupIDs = filter__teams.concat(filter__orgs).map((i) => i.value);
); $: sponsoring_contracts_show = current_runners.some(
$: generate_runners = current_runners.filter((r) => r.is_selected === true); (r) => r.is_selected === true
$: teams = []; );
$: orgs = []; $: cards_show = current_runners.some(
$: mappedteams = teams.map(function (g) { (r) => r.is_selected === true
return { value: g.id, label: g.parentGroup.name + " > " + g.name }; );
}); $: certificates_show = current_runners.some(
$: selectgroups = orgs (r) => r.is_selected === true
.map(function (g) { );
return { value: g.id, label: g.name }; $: generate_runners = current_runners.filter((r) => r.is_selected === true);
}) $: teams = [];
.concat(mappedteams); $: orgs = [];
$: mappedteams = teams.map(function (g) {
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { return { value: g.id, label: g.parentGroup.name + " > " + g.name };
teams = val; });
}); $: selectgroups = orgs
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { .map(function (g) {
orgs = val; return { value: g.id, label: g.name };
}); })
function should_display_based_on_id(id) { .concat(mappedteams);
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", "")); RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
} teams = val;
return id.toString() === searchvalue; });
} RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
</script> orgs = val;
});
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} function should_display_based_on_id(id) {
{#await runners_promise} if (searchvalue.toString().slice(-1) === "*") {
<div return id.toString().startsWith(searchvalue.replace("*", ""));
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" }
role="alert"> return id.toString() === searchvalue;
<p class="font-bold">{$_('runners-are-being-loaded')}</p> }
<p class="text-sm">{$_('this-might-take-a-moment')}</p> </script>
</div>
{:then} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
{#if current_runners.length === 0} {#await runners_promise}
<RunnersEmptyState /> <div
{:else} class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
<input role="alert">
type="search" <p class="font-bold">{$_('runners-are-being-loaded')}</p>
bind:value={searchvalue} <p class="text-sm">{$_('this-might-take-a-moment')}</p>
placeholder={$_('datatable.search')} </div>
aria-label={$_('datatable.search')} {:then}
class="gridjs-input gridjs-search-input mb-4" /> {#if current_runners.length === 0}
<div class="block mb-6"> <RunnersEmptyState />
<label {:else}
for="country" <input
class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label> type="search"
<Select bind:value={searchvalue}
on:select={(event) => { placeholder={$_('datatable.search')}
selectedFilter = event.detail; aria-label={$_('datatable.search')}
}} class="gridjs-input gridjs-search-input mb-4" />
selectedValue={selectedFilter} <div class="block mb-6">
placeholder={$_('filter-by-organization-team')} <label
containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" for="country"
items={selectgroups} class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label>
isMulti={true} /> <Select
</div> on:select={(event) => {
<div class="h-12"> selectedFilter = event.detail;
<GenerateSponsoringContracts }}
bind:sponsoring_contracts_show selectedValue={selectedFilter}
bind:generate_runners /> placeholder={$_('filter-by-organization-team')}
</div> containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
<div items={selectgroups}
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> isMulti={true} />
<table class="divide-y divide-gray-200 w-full"> </div>
<thead class="bg-gray-50"> <div class="h-12">
<tr> <GenerateSponsoringContracts
<th bind:sponsoring_contracts_show
scope="col" bind:generate_runners />
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <GenerateRunnerCards
<span bind:cards_show
on:click={() => { bind:generate_runners />
const newstate = !current_runners.some((r) => r.is_selected === true); <GenerateRunnerCertificates
current_runners = current_runners.map((r) => { bind:certificates_show
r.is_selected = newstate; bind:generate_runners />
return r; </div>
}); <div
}} class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)} <table class="divide-y divide-gray-200 w-full">
{$_('deselect-all')} <thead class="bg-gray-50">
{:else}{$_('select-all')}{/if} <tr>
</span> <th
</th> scope="col"
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
scope="col" <span
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> on:click={() => {
{$_('name')} const newstate = !current_runners.some((r) => r.is_selected === true);
</th> current_runners = current_runners.map((r) => {
<th r.is_selected = newstate;
scope="col" return r;
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> });
{$_('contact-information')} }}
</th> class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)}
<th {$_('deselect-all')}
scope="col" {:else}{$_('select-all')}{/if}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> </span>
{$_('group')} </th>
</th> <th
<th scope="col"
scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> {$_('name')}
{$_('distance-in-km')} </th>
</th> <th
<th scope="col" class="relative px-6 py-3"> scope="col"
<span class="sr-only">{$_('action')}</span> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th> {$_('contact-information')}
</tr> </th>
</thead> <th
<tbody class="divide-y divide-gray-200"> scope="col"
{#each current_runners as runner} class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{#if runner.firstname {$_('group')}
.toLowerCase() </th>
.includes( <th
searchvalue.toLowerCase() scope="col"
) || runner.lastname class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
.toLowerCase() {$_('distance-in-km')}
.includes( </th>
searchvalue.toLowerCase() <th scope="col" class="relative px-6 py-3">
) || should_display_based_on_id(runner.id)} <span class="sr-only">{$_('action')}</span>
{#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0} </th>
<tr </tr>
data-rowid="user_{runner.id}" </thead>
data-groupid={runner.group.id}> <tbody class="divide-y divide-gray-200">
<td class="px-6 py-4 whitespace-nowrap"> {#each current_runners as runner}
<input {#if runner.firstname
bind:checked={runner.is_selected} .toLowerCase()
type="checkbox" .includes(
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> searchvalue.toLowerCase()
</td> ) || runner.lastname
<td class="px-6 py-4 whitespace-nowrap"> .toLowerCase()
<div class="flex items-center"> .includes(
<div class="ml-4"> searchvalue.toLowerCase()
<div class="text-sm font-medium text-gray-900"> ) || should_display_based_on_id(runner.id)}
{runner.firstname} {#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0}
{runner.middlename || ''} <tr
{runner.lastname} data-rowid="user_{runner.id}"
</div> data-groupid={runner.group.id}>
</div> <td class="px-6 py-4 whitespace-nowrap">
</div> <input
</td> bind:checked={runner.is_selected}
<td class="px-6 py-4 whitespace-nowrap"> type="checkbox"
{#if runner.email} class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
<div class="text-sm text-gray-500">{runner.email}</div> </td>
{/if} <td class="px-6 py-4 whitespace-nowrap">
{#if runner.phone} <div class="flex items-center">
<div class="text-sm text-gray-500">{runner.phone}</div> <div class="ml-4">
{/if} <div class="text-sm font-medium text-gray-900">
{#if runner.address.address1 !== null} {runner.firstname}
{runner.address.address1}<br /> {runner.middlename || ''}
{runner.address.address2 || ''}<br /> {runner.lastname}
{runner.address.postalcode} </div>
{runner.address.city} </div>
{runner.address.country} </div>
{/if} </td>
</td> <td class="px-6 py-4 whitespace-nowrap">
<td class="px-6 py-4 whitespace-nowrap"> {#if runner.email}
{#if runner.group.responseType === 'RUNNERTEAM'} <div class="text-sm text-gray-500">{runner.email}</div>
<a {/if}
href="../teams/{runner.group.id}" {#if runner.phone}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a> <div class="text-sm text-gray-500">{runner.phone}</div>
{/if} {/if}
{#if runner.group.responseType === 'RUNNERORGANIZATION'} {#if runner.address.address1 !== null}
<a {runner.address.address1}<br />
href="../orgs/{runner.group.id}" {runner.address.address2 || ''}<br />
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a> {runner.address.postalcode}
{/if} {runner.address.city}
</td> {runner.address.country}
<td class="px-6 py-4 whitespace-nowrap"> {/if}
{runner.distance} </td>
</td> <td class="px-6 py-4 whitespace-nowrap">
{#if active_deletes[runner.id] === true} {#if runner.group.responseType === 'RUNNERTEAM'}
<td <a
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> href="../teams/{runner.group.id}"
<button class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.parentGroup.name} &gt; {runner.group.name}</a>
on:click={() => { {/if}
active_deletes[runner.id] = false; {#if runner.group.responseType === 'RUNNERORGANIZATION'}
}} <a
tabindex="0" href="../orgs/{runner.group.id}"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
<button {/if}
on:click={() => { </td>
RunnerService.runnerControllerRemove(runner.id, true) <td class="px-6 py-4 whitespace-nowrap">
.then((resp) => { {runner.distance}
current_runners = current_runners.filter((obj) => obj.id !== runner.id); </td>
}) {#if active_deletes[runner.id] === true}
.catch((err) => {}); <td
}} class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
tabindex="0" <button
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> on:click={() => {
</td> active_deletes[runner.id] = false;
{:else} }}
<td tabindex="0"
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<a <button
href="./{runner.id}" on:click={() => {
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> RunnerService.runnerControllerRemove(runner.id, true)
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')} .then((resp) => {
<button current_runners = current_runners.filter((obj) => obj.id !== runner.id);
on:click={() => { })
active_deletes[runner.id] = true; .catch((err) => {});
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
{/if} </td>
</td> {:else}
{/if} <td
</tr> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
{/if} <a
{/if} href="./{runner.id}"
{/each} class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
</tbody> {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
</table> <button
</div> on:click={() => {
{/if} active_deletes[runner.id] = true;
{:catch error} }}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> tabindex="0"
<span class="inline-block align-middle mr-8"> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
<b class="capitalize">{$_('general_promise_error')}</b> {/if}
{error} </td>
</span> {/if}
</div> </tr>
{/await} {/if}
{/if} {/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@@ -23,14 +23,14 @@
throw new Error(); throw new Error();
} }
Toastify({ Toastify({
text: $_('copied-token-to-clipboard'), text: $_("copied-token-to-clipboard"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
copied = true; copied = true;
} catch (err) { } catch (err) {
Toastify({ Toastify({
text: $_('error-whyile-copying-to-clipboard'), text: $_("error-whyile-copying-to-clipboard"),
duration: 500, duration: 500,
backgroundColor: backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
@@ -75,7 +75,9 @@
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">{$_('token')}</h3> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('token')}
</h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_('the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again')} {$_('the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again')}
@@ -106,7 +108,9 @@
d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg> d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg>
</div> </div>
</div> </div>
<p class="text-gray-500 text-xs">{$_('click-to-copy-token-to-clipboard')}</p> <p class="text-gray-500 text-xs">
{$_('click-to-copy-token-to-clipboard')}
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -17,7 +17,7 @@
MeService.meControllerRemove(true) MeService.meControllerRemove(true)
.then((resp) => { .then((resp) => {
Toastify({ Toastify({
text: "Profile deleted!", text: $_('profile-deleted'),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();

View File

@@ -4,6 +4,9 @@
import { MeService } from "@odit/lfk-client-js"; import { MeService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import ConfirmProfileDeletion from "./ConfirmProfileDeletion.svelte"; import ConfirmProfileDeletion from "./ConfirmProfileDeletion.svelte";
import PasswordStrength, {
password_strong_enough_and_equal,
} from "../auth/PasswordStrength.svelte";
$: data_loaded = false; $: data_loaded = false;
$: delete_triggered = false; $: delete_triggered = false;
$: original_data = {}; $: original_data = {};
@@ -15,8 +18,10 @@
JSON.stringify(editable) === JSON.stringify(original_data) JSON.stringify(editable) === JSON.stringify(original_data)
); );
$: save_enabled = changes_performed && isEmail(editable.email); $: save_enabled = changes_performed && isEmail(editable.email);
$: update_password_enabled = $: update_password_enabled = password_strong_enough_and_equal(
password_change.length > 0 && password_change === password_confirm; password_change,
password_confirm
);
const user_promise = MeService.meControllerGet().then((data) => { const user_promise = MeService.meControllerGet().then((data) => {
data_loaded = true; data_loaded = true;
data.groups = data.groups.map((g) => g.id); data.groups = data.groups.map((g) => g.id);
@@ -45,7 +50,7 @@
function changePassword() { function changePassword() {
if (data_loaded === true && update_password_enabled) { if (data_loaded === true && update_password_enabled) {
Toastify({ Toastify({
text: $_('changing-your-password'), text: $_("changing-your-password"),
duration: 2500, duration: 2500,
}).showToast(); }).showToast();
let postdata = Object.assign({}, original_data); let postdata = Object.assign({}, original_data);
@@ -56,7 +61,7 @@
password_change = ""; password_change = "";
postdata = {}; postdata = {};
Toastify({ Toastify({
text: $_('password-changed'), text: $_("password-changed"),
duration: 2500, duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
@@ -242,10 +247,7 @@
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" 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')} /> placeholder={$_('password')} />
</div> </div>
{#if password_change != password_confirm && password_change.length > 0} <PasswordStrength bind:password_change bind:password_confirm />
<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>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6"> <div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<button <button
@@ -257,9 +259,9 @@
{$_('update-password')} {$_('update-password')}
</button> </button>
{#if update_password_enabled} {#if update_password_enabled}
<p> <p>
{$_('after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that')} {$_('after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that')}
</p> </p>
{/if} {/if}
</div> </div>
</div> </div>

View File

@@ -43,7 +43,7 @@
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: "Team is being added...", text: $_('team-is-being-added'),
duration: -1, duration: -1,
}).showToast(); }).showToast();
RunnerTeamService.runnerTeamControllerPost({ RunnerTeamService.runnerTeamControllerPost({
@@ -55,7 +55,7 @@
modal_open = false; modal_open = false;
// //
Toastify({ Toastify({
text: "Team added", text: $_('team-added'),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();

View File

@@ -16,7 +16,7 @@
RunnerTeamService.runnerTeamControllerRemove(delete_team.id, true) RunnerTeamService.runnerTeamControllerRemove(delete_team.id, true)
.then((resp) => { .then((resp) => {
Toastify({ Toastify({
text: "Team deleted", text: $_('team-deleted'),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();

View File

@@ -1,292 +1,304 @@
<script> <script>
import { import {
GroupContactService, GroupContactService,
RunnerOrganizationService, RunnerOrganizationService,
RunnerTeamService, RunnerTeamService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import store from "../../store"; import store from "../../store";
import Select from "svelte-select"; import Select from "svelte-select";
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte"; import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte"; import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte";
import Teams from "./Teams.svelte"; import Teams from "./Teams.svelte";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
let [teamdata, original, delete_team, orgs, contacts, modal_open] = [ import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
{}, import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
{}, let [teamdata, original, delete_team, orgs, contacts, modal_open] = [
{}, {},
[], {},
[], {},
false, [],
]; [],
export let params; false,
export let import_modal_open = false; ];
$: delete_triggered = false; export let params;
$: save_enabled = !data_changed && teamdata.parentGroup != null; export let import_modal_open = false;
$: data_loaded = false; $: delete_triggered = false;
$: data_changed = JSON.stringify(teamdata) === JSON.stringify(original); $: save_enabled = data_changed && teamdata.parentGroup != null;
$: sponsoring_contracts_show = true; $: data_loaded = false;
$: generate_teams = [original]; $: data_changed = !(JSON.stringify(teamdata) === JSON.stringify(original));
$: group = {}; $: sponsoring_contracts_show = true;
$: contact = {}; $: cards_show = true;
// $: certificates_show = true;
const getContactLabel = (option) => $: generate_teams = [original];
option.firstname + " " + (option.middlename || "") + " " + option.lastname; $: group = {};
const promise = RunnerTeamService.runnerTeamControllerGetOne( $: contact = {};
params.teamid //
).then((value) => { const getContactLabel = (option) =>
data_loaded = true; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
teamdata = Object.assign(teamdata, value); const promise = RunnerTeamService.runnerTeamControllerGetOne(
original = Object.assign(original, value); params.teamid
RunnerOrganizationService.runnerOrganizationControllerGetAll().then( ).then((value) => {
(val) => { data_loaded = true;
orgs = val.map((r) => { teamdata = Object.assign(teamdata, value);
return { label: r.name, value: r }; original = Object.assign(original, value);
}); RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
group = orgs.find((g) => g.value.id == teamdata.parentGroup.id); (val) => {
} orgs = val.map((r) => {
); delete r.contact;
GroupContactService.groupContactControllerGetAll().then((val) => { r.teams = [];
contacts = val.map((r) => { return { label: r.name, value: r };
return { label: getContactLabel(r), value: r }; });
}); group = orgs.find((g) => g.value.id == teamdata.parentGroup.id);
if (teamdata.contact) { }
contact = contacts.find((g) => g.value.id == teamdata.contact.id); );
} else { GroupContactService.groupContactControllerGetAll().then((val) => {
contact = null; contacts = val.map((r) => {
} return { label: getContactLabel(r), value: r };
}); });
}); if (teamdata.contact) {
function deleteTeam() { contact = contacts.find((g) => g.value.id == teamdata.contact.id);
RunnerTeamService.runnerTeamControllerRemove(original.id, false) } else {
.then((resp) => { contact = null;
Toastify({ }
text: "Organization deleted", });
duration: 500, });
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", function deleteTeam() {
}).showToast(); RunnerTeamService.runnerTeamControllerRemove(original.id, false)
location.replace("./"); .then((resp) => {
}) Toastify({
.catch((err) => { text: $_('team-deleted'),
modal_open = true; duration: 500,
delete_team = original; backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}); }).showToast();
} location.replace("./");
function submit() { })
if (data_loaded === true && save_enabled) { .catch((err) => {
Toastify({ modal_open = true;
text: "updating team", delete_team = original;
duration: 2500, });
}).showToast(); }
let postdata = teamdata; function submit() {
postdata.parentGroup = teamdata.parentGroup.id; if (data_loaded === true && save_enabled) {
postdata.contact = teamdata.contact?.id; Toastify({
RunnerTeamService.runnerTeamControllerPut(original.id, postdata) text: $_('updating-team'),
.then((resp) => { duration: 2500,
Object.assign(original, teamdata); }).showToast();
original = original; let postdata = teamdata;
Toastify({ postdata.parentGroup = teamdata.parentGroup.id;
text: "updated team", postdata.contact = teamdata.contact?.id;
duration: 2500, RunnerTeamService.runnerTeamControllerPut(original.id, postdata)
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", .then((resp) => {
}).showToast(); Object.assign(original, teamdata);
}) original = original;
.catch((err) => {}); Toastify({
} text: $_('updated-team'),
} duration: 2500,
</script> backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
<ImportRunnerModal })
current_runners={[]} .catch((err) => {});
on:cancelDelete={(event) => { }
import_modal_open = false; }
}} </script>
passed_team={teamdata}
passed_orgs={[]} <ImportRunnerModal
passed_org={{}} current_runners={[]}
opened_from="TeamDetail" on:cancelDelete={(event) => {
bind:import_modal_open /> import_modal_open = false;
<ConfirmTeamDeletion bind:modal_open bind:delete_team /> }}
{#if data_loaded} passed_team={teamdata}
<section class="container p-5"> passed_orgs={[]}
<div class="mb-8 text-3xl font-extrabold leading-tight"> passed_org={{}}
{original.name} opened_from="TeamDetail"
<span data-id="org_actions_${teamdata.id}"> bind:import_modal_open />
<GenerateSponsoringContracts <ConfirmTeamDeletion bind:modal_open bind:delete_team />
bind:sponsoring_contracts_show {#if data_loaded}
bind:generate_teams /> <section class="container p-5">
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} <div class="mb-8 text-3xl font-extrabold leading-tight">
<button {original.name}
on:click={() => { <span data-id="org_actions_${teamdata.id}">
import_modal_open = true; <GenerateSponsoringContracts
}} bind:sponsoring_contracts_show
type="button" bind:generate_teams />
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"> <GenerateRunnerCards
{$_('import-runners')} bind:cards_show
</button> bind:generate_teams />
{/if} <GenerateRunnerCertificates
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')} bind:certificates_show
{#if delete_triggered} bind:generate_teams />
<button {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
on:click={deleteTeam} <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">{$_('confirm-delete')}</button> on:click={() => {
<button import_modal_open = true;
on:click={() => { }}
delete_triggered = !delete_triggered; type="button"
}} class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
class="w-full 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> {$_('import-runners')}
{/if} </button>
{#if !delete_triggered} {/if}
<button {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
on:click={() => { {#if delete_triggered}
delete_triggered = true; <button
}} on:click={deleteTeam}
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">{$_('confirm-delete')}</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-team')}</button> <button
{/if} on:click={() => {
{/if} delete_triggered = !delete_triggered;
{#if !delete_triggered} }}
<button class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
on:click={submit} {/if}
disabled={!save_enabled} {#if !delete_triggered}
class:opacity-50={!save_enabled} <button
type="button" on:click={() => {
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> delete_triggered = true;
{/if} }}
</span> type="button"
</div> 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-team')}</button>
<div class="flex flex-row mb-4"> {/if}
<div class="w-full"> {/if}
<nav class="w-full flex"> {#if !delete_triggered}
<ol class="list-none flex flex-row items-center justify-start"> <button
<li class="mr-2 flex items-center"> on:click={submit}
<svg disabled={!save_enabled}
stroke="currentColor" class:opacity-50={!save_enabled}
fill="none" type="button"
stroke-width="2" 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>
viewBox="0 0 24 24" {/if}
stroke-linecap="round" </span>
stroke-linejoin="round" </div>
class="h-3 w-3 stroke-current" <div class="flex flex-row mb-4">
height="1em" <div class="w-full">
width="1em" <nav class="w-full flex">
xmlns="http://www.w3.org/2000/svg"><path <ol class="list-none flex flex-row items-center justify-start">
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> <li class="mr-2 flex items-center">
<polyline points="9 22 9 12 15 12 15 22" /></svg> <svg
</li> stroke="currentColor"
<li class="flex items-center"> fill="none"
<a class="mr-2" href="/">Home</a><svg stroke-width="2"
stroke="currentColor" viewBox="0 0 24 24"
fill="none" stroke-linecap="round"
stroke-width="2" stroke-linejoin="round"
viewBox="0 0 24 24" class="h-3 w-3 stroke-current"
stroke-linecap="round" height="1em"
stroke-linejoin="round" width="1em"
class="h-3 w-3 mr-2 stroke-current" xmlns="http://www.w3.org/2000/svg"><path
height="1em" d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
width="1em" <polyline points="9 22 9 12 15 12 15 22" /></svg>
xmlns="http://www.w3.org/2000/svg"><line </li>
x1="5" <li class="flex items-center">
y1="12" <a class="mr-2" href="/">Home</a><svg
x2="19" stroke="currentColor"
y2="12" /> fill="none"
<polyline points="12 5 19 12 12 19" /></svg> stroke-width="2"
</li> viewBox="0 0 24 24"
<li class="mr-2 flex items-center"> stroke-linecap="round"
<svg stroke-linejoin="round"
class="flex-shrink-0 w-5 h-5 mr-2" class="h-3 w-3 mr-2 stroke-current"
fill="currentColor" height="1em"
width="24" width="1em"
height="24" xmlns="http://www.w3.org/2000/svg"><line
xmlns="http://www.w3.org/2000/svg" x1="5"
viewBox="0 0 640 512"><path y1="12"
fill="currentColor" x2="19"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> y2="12" />
</li> <polyline points="12 5 19 12 12 19" /></svg>
<li class="flex items-center"> </li>
<a class="mr-2" href="./">Teams</a><svg <li class="mr-2 flex items-center">
stroke="currentColor" <svg
fill="none" class="flex-shrink-0 w-5 h-5 mr-2"
stroke-width="2" fill="currentColor"
viewBox="0 0 24 24" width="24"
stroke-linecap="round" height="24"
stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"
class="h-3 w-3 mr-2 stroke-current" viewBox="0 0 640 512"><path
height="1em" fill="currentColor"
width="1em" d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
xmlns="http://www.w3.org/2000/svg"><line </li>
x1="5" <li class="flex items-center">
y1="12" <a class="mr-2" href="./">Teams</a><svg
x2="19" stroke="currentColor"
y2="12" /> fill="none"
<polyline points="12 5 19 12 12 19" /></svg> stroke-width="2"
</li> viewBox="0 0 24 24"
<li class="flex items-center"> stroke-linecap="round"
<span class="mr-2">Team-Details #{params.teamid}</span> stroke-linejoin="round"
</li> class="h-3 w-3 mr-2 stroke-current"
</ol> height="1em"
</nav> width="1em"
</div> xmlns="http://www.w3.org/2000/svg"><line
</div> x1="5"
<div class="text-sm w-full"> y1="12"
<label for="name" class="font-medium text-gray-700">Name</label> x2="19"
<input y2="12" />
autocomplete="off" <polyline points="12 5 19 12 12 19" /></svg>
placeholder="Name" </li>
type="text" <li class="flex items-center">
bind:value={teamdata.name} <span class="mr-2">Team-Details #{params.teamid}</span>
name="name" </li>
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" /> </ol>
</div> </nav>
<div class="text-sm w-full"> </div>
<label </div>
for="contact" <div class="text-sm w-full">
class="font-medium text-gray-700">{$_('contact')}</label> <label for="name" class="font-medium text-gray-700">Name</label>
<Select <input
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" autocomplete="off"
itemFilter={(label, filterText, option) => label placeholder="Name"
.toLowerCase() type="text"
.includes( bind:value={teamdata.name}
filterText.toLowerCase() name="name"
) || option.value.id 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" />
.toString() </div>
.startsWith(filterText.toLowerCase())} <div class="text-sm w-full">
items={contacts} <label
showChevron={true} for="contact"
placeholder={$_('no-contact-selected')} class="font-medium text-gray-700">{$_('contact')}</label>
noOptionsMessage={$_('no-contact-found')} <Select
bind:selectedValue={contact} 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"
on:select={(selectedValue) => (teamdata.contact = selectedValue.detail.value)} itemFilter={(label, filterText, option) => label
on:clear={() => (teamdata.contact = null)} /> .toLowerCase()
</div> .includes(
<div class="text-sm w-full"> filterText.toLowerCase()
<label ) || option.value.id
for="org" .toString()
class="font-medium text-gray-700">{$_('organization')}</label> .startsWith(filterText.toLowerCase())}
<Select items={contacts}
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" showChevron={true}
itemFilter={(label, filterText, option) => label placeholder={$_('no-contact-selected')}
.toLowerCase() noOptionsMessage={$_('no-contact-found')}
.includes( bind:selectedValue={contact}
filterText.toLowerCase() on:select={(selectedValue) => (teamdata.contact = selectedValue.detail.value)}
) || option.id.value on:clear={() => (teamdata.contact = null)} />
.toString() </div>
.startsWith(filterText.toLowerCase())} <div class="text-sm w-full">
items={orgs} <label
showChevron={true} for="org"
placeholder={$_('search-for-an-organization-by-name-or-id')} class="font-medium text-gray-700">{$_('organization')}</label>
noOptionsMessage={$_('no-organizations-found')} <Select
bind:selectedValue={group} containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
on:select={(selectedValue) => (teamdata.parentGroup = selectedValue.detail.value)} itemFilter={(label, filterText, option) => label
on:clear={() => (teamdata.parentGroup = null)} /> .toLowerCase()
</div> .includes(
</section> filterText.toLowerCase()
{:else} ) || option.id.value
{#await promise} .toString()
{$_('team-detail-is-being-loaded')} .startsWith(filterText.toLowerCase())}
{:catch error} items={orgs}
<PromiseError /> showChevron={true}
{/await} placeholder={$_('search-for-an-organization-by-name-or-id')}
{/if} noOptionsMessage={$_('no-organizations-found')}
bind:selectedValue={group}
on:select={(selectedValue) => (teamdata.parentGroup = selectedValue.detail.value)}
on:clear={() => (teamdata.parentGroup = null)} />
</div>
</section>
{:else}
{#await promise}
{$_('team-detail-is-being-loaded')}
{:catch error}
<PromiseError />
{/await}
{/if}

View File

@@ -1,210 +1,224 @@
<script> <script>
import { getLocaleFromNavigator, t, _ } from "svelte-i18n"; import { getLocaleFromNavigator, t, _ } from "svelte-i18n";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { RunnerTeamService } from "@odit/lfk-client-js"; import { RunnerTeamService } from "@odit/lfk-client-js";
const teams_promise = RunnerTeamService.runnerTeamControllerGetAll(); const teams_promise = RunnerTeamService.runnerTeamControllerGetAll();
import store, { users as usersstore } from "../../store.js"; import store, { users as usersstore } from "../../store.js";
import TeamsEmptyState from "./TeamsEmptyState.svelte"; import TeamsEmptyState from "./TeamsEmptyState.svelte";
import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte"; import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
$: searchvalue = ""; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: active_deletes = []; import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
$: sponsoring_contracts_show = current_teams.some( $: searchvalue = "";
(r) => r.is_selected === true $: active_deletes = [];
); $: sponsoring_contracts_show = current_teams.some(
$: generate_teams = current_teams.filter((r) => r.is_selected === true); (r) => r.is_selected === true
export let current_teams = []; );
let modal_open = false; $: cards_show = current_teams.some(
let delete_team = {}; (r) => r.is_selected === true
usersstore.subscribe((val) => { );
current_teams = val; $: certificates_show = current_teams.some(
}); (r) => r.is_selected === true
teams_promise.then((data) => { );
usersstore.set(data); $: generate_teams = current_teams.filter((r) => r.is_selected === true);
}); export let current_teams = [];
</script> let modal_open = false;
let delete_team = {};
<ConfirmTeamDeletion usersstore.subscribe((val) => {
on:cancelDelete={(event) => { current_teams = val;
modal_open = false; });
active_deletes[event.detail.id] = false; teams_promise.then((data) => {
}} usersstore.set(data);
bind:modal_open });
bind:delete_team /> </script>
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
{#await teams_promise} <ConfirmTeamDeletion
<div on:cancelDelete={(event) => {
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" modal_open = false;
role="alert"> active_deletes[event.detail.id] = false;
<p class="font-bold">{$_('teams-are-being-loaded')}</p> }}
<p class="text-sm">{$_('this-might-take-a-moment')}</p> bind:modal_open
</div> bind:delete_team />
{:then} {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
{#if current_teams.length === 0} {#await teams_promise}
<TeamsEmptyState /> <div
{:else} class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
<input role="alert">
type="search" <p class="font-bold">{$_('teams-are-being-loaded')}</p>
bind:value={searchvalue} <p class="text-sm">{$_('this-might-take-a-moment')}</p>
placeholder={$_('datatable.search')} </div>
aria-label={$_('datatable.search')} {:then}
class="gridjs-input gridjs-search-input mb-4" /> {#if current_teams.length === 0}
<div class="h-12"> <TeamsEmptyState />
<GenerateSponsoringContracts {:else}
bind:sponsoring_contracts_show <input
bind:generate_teams /> type="search"
</div> bind:value={searchvalue}
<div placeholder={$_('datatable.search')}
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> aria-label={$_('datatable.search')}
<table class="divide-y divide-gray-200 w-full"> class="gridjs-input gridjs-search-input mb-4" />
<thead class="bg-gray-50"> <div class="h-12">
<tr> <GenerateSponsoringContracts
<th bind:sponsoring_contracts_show
scope="col" bind:generate_teams />
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <GenerateRunnerCards
<span bind:cards_show
on:click={() => { bind:generate_teams />
const newstate = !current_teams.some((r) => r.is_selected === true); <GenerateRunnerCertificates
current_teams = current_teams.map((r) => { bind:certificates_show
r.is_selected = newstate; bind:generate_teams />
return r; </div>
}); <div
}} class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
class="underline cursor-pointer select-none">{#if current_teams.some((r) => r.is_selected === true)} <table class="divide-y divide-gray-200 w-full">
{$_('deselect-all')} <thead class="bg-gray-50">
{:else}{$_('select-all')}{/if} <tr>
</span> <th
</th> scope="col"
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
scope="col" <span
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> on:click={() => {
{$_('name')} const newstate = !current_teams.some((r) => r.is_selected === true);
</th> current_teams = current_teams.map((r) => {
<th r.is_selected = newstate;
scope="col" return r;
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> });
{$_('organization')} }}
</th> class="underline cursor-pointer select-none">{#if current_teams.some((r) => r.is_selected === true)}
<th {$_('deselect-all')}
scope="col" {:else}{$_('select-all')}{/if}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> </span>
{$_('contact')} </th>
</th> <th
<th scope="col" class="relative px-6 py-3"> scope="col"
<span class="sr-only">{$_('action')}</span> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th> {$_('name')}
</tr> </th>
</thead> <th
<tbody class="divide-y divide-gray-200"> scope="col"
{#each current_teams as t} class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{#if Object.values(t) {$_('organization')}
.toString() </th>
.toLowerCase() <th
.includes(searchvalue)} scope="col"
<tr data-rowid="team_{t.id}"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<td class="px-6 py-4 whitespace-nowrap"> {$_('contact')}
<input </th>
bind:checked={t.is_selected} <th scope="col" class="relative px-6 py-3">
type="checkbox" <span class="sr-only">{$_('action')}</span>
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> </th>
</td> </tr>
<td class="px-6 py-4 whitespace-nowrap"> </thead>
<div class="flex items-center"> <tbody class="divide-y divide-gray-200">
<div class="ml-4"> {#each current_teams as t}
<div class="text-sm font-medium text-gray-900"> {#if Object.values(t)
{t.name} .toString()
</div> .toLowerCase()
</div> .includes(searchvalue)}
</div> <tr data-rowid="team_{t.id}">
</td> <td class="px-6 py-4 whitespace-nowrap">
<td class="px-6 py-4 whitespace-nowrap"> <input
<div class="flex items-center"> bind:checked={t.is_selected}
<div class="ml-4"> type="checkbox"
<div class="text-sm font-medium text-gray-900"> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
{#if t.parentGroup} </td>
<a <td class="px-6 py-4 whitespace-nowrap">
href="../orgs/{t.parentGroup.id}" <div class="flex items-center">
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.parentGroup.name}</a> <div class="ml-4">
{:else}{$_('no-organization-specified')}{/if} <div class="text-sm font-medium text-gray-900">
</div> {t.name}
</div> </div>
</div> </div>
</td> </div>
<td class="px-6 py-4 whitespace-nowrap"> </td>
<div class="flex items-center"> <td class="px-6 py-4 whitespace-nowrap">
<div class="ml-4"> <div class="flex items-center">
<div class="text-sm font-medium text-gray-900"> <div class="ml-4">
{#if t.contact} <div class="text-sm font-medium text-gray-900">
<a {#if t.parentGroup}
href="../contacts/{t.contact.id}" <a
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.contact.firstname} href="../orgs/{t.parentGroup.id}"
{t.contact.middlename || ''} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.parentGroup.name}</a>
{t.contact.lastname}</a> {:else}{$_('no-organization-specified')}{/if}
{:else}{$_('no-contact-specified')}{/if} </div>
</div> </div>
</div> </div>
</div> </td>
</td> <td class="px-6 py-4 whitespace-nowrap">
{#if active_deletes[t.id] === true} <div class="flex items-center">
<td <div class="ml-4">
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <div class="text-sm font-medium text-gray-900">
<button {#if t.contact}
on:click={() => { <a
active_deletes[t.id] = false; href="../contacts/{t.contact.id}"
}} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.contact.firstname}
tabindex="0" {t.contact.middlename || ''}
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel {t.contact.lastname}</a>
Delete</button> {:else}{$_('no-contact-specified')}{/if}
<button </div>
on:click={() => { </div>
RunnerTeamService.runnerTeamControllerRemove(t.id, false) </div>
.then((resp) => { </td>
current_teams = current_teams.filter((obj) => obj.id !== t.id); {#if active_deletes[t.id] === true}
Toastify({ <td
text: $_('organization-deleted'), class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
duration: 500, <button
backgroundColor: on:click={() => {
'linear-gradient(to right, #00b09b, #96c93d)', active_deletes[t.id] = false;
}).showToast(); }}
}) tabindex="0"
.catch((err) => { class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel
modal_open = true; Delete</button>
delete_team = t; <button
}); on:click={() => {
}} RunnerTeamService.runnerTeamControllerRemove(t.id, false)
tabindex="0" .then((resp) => {
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> current_teams = current_teams.filter((obj) => obj.id !== t.id);
</td> Toastify({
{:else} text: $_('organization-deleted'),
<td duration: 500,
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> backgroundColor:
<a 'linear-gradient(to right, #00b09b, #96c93d)',
href="./{t.id}" }).showToast();
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> })
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')} .catch((err) => {
<button modal_open = true;
on:click={() => { delete_team = t;
active_deletes[t.id] = true; });
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
{/if} </td>
</td> {:else}
{/if} <td
</tr> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
{/if} <a
{/each} href="./{t.id}"
</tbody> class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
</table> {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
</div> <button
{/if} on:click={() => {
{:catch error} active_deletes[t.id] = true;
<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"> tabindex="0"
<b class="capitalize">{$_('general_promise_error')}</b> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{error} {/if}
</span> </td>
</div> {/if}
{/await} </tr>
{/if} {/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@@ -34,7 +34,7 @@
`[data-id="triggered_table_actions_${trackid}"]` `[data-id="triggered_table_actions_${trackid}"]`
).parentNode.parentNode.parentNode; ).parentNode.parentNode.parentNode;
Toastify({ Toastify({
text: "Track is being updated...", text: $_('track-is-being-updated'),
duration: 500, duration: 500,
}).showToast(); }).showToast();
TrackService.trackControllerPut(trackid, { TrackService.trackControllerPut(trackid, {
@@ -45,7 +45,7 @@
}) })
.then((r) => { .then((r) => {
Toastify({ Toastify({
text: "Track was updated!", text: $_('track-was-updated'),
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
duration: 1000, duration: 1000,
}).showToast(); }).showToast();

View File

@@ -5,6 +5,9 @@
import { UserService } from "@odit/lfk-client-js"; import { UserService } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import PasswordStrength, {
password_strong_enough,
} from "../auth/PasswordStrength.svelte";
export let modal_open; export let modal_open;
export let current_users; export let current_users;
let firstname_input; let firstname_input;
@@ -28,7 +31,10 @@
$: isLastnameValid = lastname_input_value.trim().length !== 0; $: isLastnameValid = lastname_input_value.trim().length !== 0;
$: isFirstnameValid = firstname_input_value.trim().length !== 0; $: isFirstnameValid = firstname_input_value.trim().length !== 0;
$: createbtnenabled = $: createbtnenabled =
isFirstnameValid && isLastnameValid && isPasswordValid && isEmailValid; isFirstnameValid &&
isLastnameValid &&
password_strong_enough(password_input_value) &&
isEmailValid;
(function () { (function () {
document.onkeydown = function (e) { document.onkeydown = function (e) {
e = e || window.event; e = e || window.event;
@@ -203,12 +209,8 @@
type="password" type="password"
name="password" name="password"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isPasswordValid} <PasswordStrength
<span bind:password_change={password_input_value} />
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('password-is-required')}
</span>
{/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label

View File

@@ -27,7 +27,7 @@
}); });
function submit() { function submit() {
Toastify({ Toastify({
text: "updating permissions...", text: $_('updating-permissions'),
duration: 2500, duration: 2500,
}).showToast(); }).showToast();
to_delete.forEach((d) => { to_delete.forEach((d) => {

View File

@@ -1,14 +0,0 @@
import App from './App.svelte';
const app = new App({
target: document.body
});
export default app;
// HMR
if (import.meta.hot) {
import.meta.hot.accept();
import.meta.hot.dispose(() => {
app.$destroy();
});
}

View File

@@ -22,6 +22,7 @@
"add-your-fist-donation": "Erstelle dein erstes Sponsoring", "add-your-fist-donation": "Erstelle dein erstes Sponsoring",
"add-your-fist-scan": "Füge deinen ersten Scan hinzu", "add-your-fist-scan": "Füge deinen ersten Scan hinzu",
"adding-card": "Karte wird erstellt", "adding-card": "Karte wird erstellt",
"adding-donation": "Sponsoring wird erstellt...",
"adding-scan": "Scan wird hinzugefügt", "adding-scan": "Scan wird hinzugefügt",
"address": "Adresse", "address": "Adresse",
"address-is-required": "Du musst eine Adresse angeben", "address-is-required": "Du musst eine Adresse angeben",
@@ -30,6 +31,7 @@
"all-associated-donations-will-get-deleted-as-well": "Alle Sponsorings dieser Sponsor:in werden ebenfalls gelöscht", "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-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!", "all-associated-teams-and-runners-will-be-deleted-too": "Alle assoziierten Teams und Läufer:innen werden auch gelöscht!",
"already-paid": "Bereits bezahlt",
"amount": "Anzahl", "amount": "Anzahl",
"amount-per-kilometer": "Betrag pro Kilometer", "amount-per-kilometer": "Betrag pro Kilometer",
"apartment-suite-etc": "Apartment, Wohnung, etc.", "apartment-suite-etc": "Apartment, Wohnung, etc.",
@@ -50,9 +52,11 @@
"card-deleted": "Karte gelöscht", "card-deleted": "Karte gelöscht",
"card-updated": "Karte aktualisiert", "card-updated": "Karte aktualisiert",
"cards": "Läuferkarten", "cards": "Läuferkarten",
"certificates": "Urkunden",
"change-your-password-here": "Hier kannst du dein Passwort ändern", "change-your-password-here": "Hier kannst du dein Passwort ändern",
"changing-your-password": "Passwort wird geändert", "changing-your-password": "Passwort wird geändert",
"city": "Stadt", "city": "Stadt",
"click-to-copy-the-link-into-your-clipboard": "Klicke auf den Link, um ihn in deine Zwischenablage zu kopieren",
"click-to-copy-token-to-clipboard": "Klicke auf den Token, um ihn in deine Zwischenablage zu kopieren", "click-to-copy-token-to-clipboard": "Klicke auf den Token, um ihn in deine Zwischenablage zu kopieren",
"close": "Schließen", "close": "Schließen",
"code": "Code", "code": "Code",
@@ -66,12 +70,15 @@
"confirm-deletion": "Löschung Bestätigen", "confirm-deletion": "Löschung Bestätigen",
"confirm-the-new-password": "Neues Passwort bestätigen", "confirm-the-new-password": "Neues Passwort bestätigen",
"contact": "Kontakt", "contact": "Kontakt",
"contact-added": "Kontakt wurde hinzugefügt",
"contact-deleted": "Kontakt gelöscht", "contact-deleted": "Kontakt gelöscht",
"contact-information": "Kontaktinformation", "contact-information": "Kontaktinformation",
"contact-is-being-added": "Kontakt wird erstellt...",
"contact-is-being-updated": "Kontakt wird aktualisiert ...", "contact-is-being-updated": "Kontakt wird aktualisiert ...",
"contact-is-not-a-member-in-any-group": "Kontakt gehört zu keiner Gruppe", "contact-is-not-a-member-in-any-group": "Kontakt gehört zu keiner Gruppe",
"contacts": "Kontakte", "contacts": "Kontakte",
"contacts-are-being-loaded": "Kontakte werden geladen ...", "contacts-are-being-loaded": "Kontakte werden geladen ...",
"copied-link-to-clipboard": "Link wurde in die Zwischenablage kopiert",
"copied-token-to-clipboard": "Token wurde in die Zwischenablage kopiert", "copied-token-to-clipboard": "Token wurde in die Zwischenablage kopiert",
"count_organizations": "Organisationen (Anzahl)", "count_organizations": "Organisationen (Anzahl)",
"count_teams": "Teams (Anzahl)", "count_teams": "Teams (Anzahl)",
@@ -90,12 +97,14 @@
"create-a-new-track": "Neuen Track erstellen", "create-a-new-track": "Neuen Track erstellen",
"create-a-new-user": "Neue Benutzer:in anlegen", "create-a-new-user": "Neue Benutzer:in anlegen",
"create-a-new-user-group": "Erstelle eine neue Gruppe", "create-a-new-user-group": "Erstelle eine neue Gruppe",
"create-and-generate-pdf": "Erstellen und PDF herunterladen",
"create-bulk-blanco-cards": "Blankokarten erstellen", "create-bulk-blanco-cards": "Blankokarten erstellen",
"create-bulk-cards": "Blankokarten erstellen", "create-bulk-cards": "Blankokarten erstellen",
"create-organization": "Organisation erstellen", "create-organization": "Organisation erstellen",
"create-team": "Team erstellen", "create-team": "Team erstellen",
"create-track": "Track erstellen", "create-track": "Track erstellen",
"create-user": "Benutzer anlegen", "create-user": "Benutzer anlegen",
"create-without-pdf": "Ohne PDF erstellen",
"created-blanco-cards": "Blankokarten wurden erstellt", "created-blanco-cards": "Blankokarten wurden erstellt",
"creating-blanco-cards": "Erstelle Blankokarten", "creating-blanco-cards": "Erstelle Blankokarten",
"credits": "Credits", "credits": "Credits",
@@ -124,7 +133,7 @@
}, },
"delete": "Löschen", "delete": "Löschen",
"delete-contact": "Kontakt löschen", "delete-contact": "Kontakt löschen",
"delete-donation": "Sponsporing löschen", "delete-donation": "Sponsoring löschen",
"delete-donor": "Sponsor:in löschen", "delete-donor": "Sponsor:in löschen",
"delete-group": "Gruppe löschen", "delete-group": "Gruppe löschen",
"delete-organization": "Organisation löschen", "delete-organization": "Organisation löschen",
@@ -152,6 +161,9 @@
"documentation": "Dokumentation", "documentation": "Dokumentation",
"donation-amount": "Sponsoringbetrag", "donation-amount": "Sponsoringbetrag",
"donation-amount-must-be-greater-that-0-00eur": "Der Sponsoringbetrag muss größer als 0.00€ sein.", "donation-amount-must-be-greater-that-0-00eur": "Der Sponsoringbetrag muss größer als 0.00€ sein.",
"donation-deleted": "Sponsoring gelöscht",
"donation-updated": "Sponsoring wurde aktualisiert",
"donation_added": "Sponsoring hinzugefügt",
"donations": "Sponsorings", "donations": "Sponsorings",
"donor": "Sponsor:in", "donor": "Sponsor:in",
"donor-added": "Sponsor:in hinzugefügt", "donor-added": "Sponsor:in hinzugefügt",
@@ -171,6 +183,7 @@
"enabled": "aktiviert", "enabled": "aktiviert",
"enabled_large": "Aktiviert", "enabled_large": "Aktiviert",
"english": "Englisch", "english": "Englisch",
"enter-payment": "Zahlung eingeben",
"error-during-import": "Fehler beim Importieren", "error-during-import": "Fehler beim Importieren",
"error-whyile-copying-to-clipboard": "Fehler beim Kopieren in die Zwischenablage", "error-whyile-copying-to-clipboard": "Fehler beim Kopieren in die Zwischenablage",
"error_on_login": "😢Fehler beim Login", "error_on_login": "😢Fehler beim Login",
@@ -178,6 +191,7 @@
"everything-concerning-your-profile": "Alles zu deinem Profil", "everything-concerning-your-profile": "Alles zu deinem Profil",
"everything-is-more-fun-together": "Im Team macht's mehr Spaß 🏃‍♂️🏃‍♀️🏃‍♂️", "everything-is-more-fun-together": "Im Team macht's mehr Spaß 🏃‍♂️🏃‍♀️🏃‍♂️",
"faq": "FAQ", "faq": "FAQ",
"filename_sponsoringquittungsliste": "SponsoringQuittungsListe",
"filter-by-organization-team": "Filtern nach Organisation / Team", "filter-by-organization-team": "Filtern nach Organisation / Team",
"first-name": "Vorname", "first-name": "Vorname",
"first-name-is-required": "Vorname muss angegeben werden", "first-name-is-required": "Vorname muss angegeben werden",
@@ -187,9 +201,12 @@
"geerbte": "geerbte", "geerbte": "geerbte",
"general-stats": "Allgemeine Statistiken", "general-stats": "Allgemeine Statistiken",
"general_promise_error": "😢 Ein unbekannter Fehler ist aufgetreten", "general_promise_error": "😢 Ein unbekannter Fehler ist aufgetreten",
"generate-runner-certificate": "Urkunde generieren",
"generate-runner-certificates": "Urkunden generieren",
"generate-runnercards": "Läuferkarten generieren",
"generate-sponsoring-contract": "Sponsoringvertrag generieren", "generate-sponsoring-contract": "Sponsoringvertrag generieren",
"generate-sponsoring-contracts": "Sponsoringverträge generieren", "generate-sponsoring-contracts": "Sponsoringverträge generieren",
"generating-pdf": "Pdf wird generiert...", "generating-pdf": "PDF wird generiert...",
"generating-pdfs": "PDFs werden generiert...", "generating-pdfs": "PDFs werden generiert...",
"generic-ui-logic-error": "Etwas ist in der Benutzeroberfläche schiefgelaufen.", "generic-ui-logic-error": "Etwas ist in der Benutzeroberfläche schiefgelaufen.",
"german": "Deutsch", "german": "Deutsch",
@@ -216,7 +233,7 @@
"internal-error": "Interner Fehler", "internal-error": "Interner Fehler",
"invalid": "Ungültig", "invalid": "Ungültig",
"invalid-mail-reset": "Das ist keine gültige E-Mail", "invalid-mail-reset": "Das ist keine gültige E-Mail",
"just-enter-how-many-you-want-and-the-system-will-create-them": "Geb einfach ein, wie viele Blankokarten das System erstellen soll.", "just-enter-how-many-you-want-and-the-system-will-create-them": "Gebe einfach ein, wie viele Blankokarten das System erstellen soll.",
"laeufer-hinzufuegen": "Läufer:in hinzufügen", "laeufer-hinzufuegen": "Läufer:in hinzufügen",
"laeufer-importieren": "Läufer:innen importieren", "laeufer-importieren": "Läufer:innen importieren",
"laptime": "Rundenzeit", "laptime": "Rundenzeit",
@@ -242,6 +259,10 @@
"middle-name": "Mittelname", "middle-name": "Mittelname",
"minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)", "minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)",
"minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein", "minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein",
"must-be-at-least-10-characters-long": "Passwort muss mindestens 10 Zeichen lang sein!",
"must-contain-a-lowercase-letter": "Passwort muss einen Großbuchstaben enthalten!",
"must-contain-a-number": "Passwort muss eine Zahl enthalten!",
"must-contain-a-uppercase-letter": "Passwort muss einen Kleinbuchstaben enthalten!",
"name": "Name", "name": "Name",
"name-is-required": "Der Gruppenname muss angegeben werden", "name-is-required": "Der Gruppenname muss angegeben werden",
"new-password": "Neues Passwort", "new-password": "Neues Passwort",
@@ -256,6 +277,7 @@
"no-runners-found": "Keine Läufer:innen gefunden", "no-runners-found": "Keine Läufer:innen gefunden",
"no-tracks-added-yet": "Es wurden noch keine Tracks erstellt.", "no-tracks-added-yet": "Es wurden noch keine Tracks erstellt.",
"non-blanko": "Keine/Blankokarte", "non-blanko": "Keine/Blankokarte",
"open": "OFFEN",
"organization": "Organisation", "organization": "Organisation",
"organization-added": "Organisation hinzugefügt", "organization-added": "Organisation hinzugefügt",
"organization-deleted": "Organisation gelöscht", "organization-deleted": "Organisation gelöscht",
@@ -266,6 +288,8 @@
"organizations-are-being-loaded": "Organisationen werden geladen ...", "organizations-are-being-loaded": "Organisationen werden geladen ...",
"orgs": "Organisationen", "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!", "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!",
"paid": "BEZAHLT",
"paid-amount": "Gezahlter Betrag",
"password": "Passwort", "password": "Passwort",
"password-changed": "Passwort wurde aktualisiert!", "password-changed": "Passwort wurde aktualisiert!",
"password-is-required": "Passwort muss angegeben werden", "password-is-required": "Passwort muss angegeben werden",
@@ -273,7 +297,8 @@
"password-reset-in-progress": "Passwort wird zurückgesetzt...", "password-reset-in-progress": "Passwort wird zurückgesetzt...",
"password-reset-mail-sent": "Passwort-Reset Mail wurde an \"{usersEmail}\" geschickt.", "password-reset-mail-sent": "Passwort-Reset Mail wurde an \"{usersEmail}\" geschickt.",
"password-reset-successful": "Passwort erfolgreich zurückgesetzt!", "password-reset-successful": "Passwort erfolgreich zurückgesetzt!",
"passwords-dont-match": "Die Passwörter stimmen nicht überein.", "passwords-dont-match": "Die Passwörter stimmen nicht überein!",
"payment-amount-must-be-greater-than-0-00eur": "Der Zahlungsbetrag muss größer als 0.00€ sein!",
"pdf-generation-failed": "PDF Generierung fehlgeschlagen!", "pdf-generation-failed": "PDF Generierung fehlgeschlagen!",
"pdf-successfully-generated": "PDF wurde erfolgreich generiert!", "pdf-successfully-generated": "PDF wurde erfolgreich generiert!",
"pdfs-successfully-generated": "Alle PDFs wurden generiert!", "pdfs-successfully-generated": "Alle PDFs wurden generiert!",
@@ -296,9 +321,11 @@
"please-provide-the-required-information-to-add-a-new-user": "Bitte gebe alle nötigen Informationen an, im die neue Benutzer:in zu erstellen.", "please-provide-the-required-information-to-add-a-new-user": "Bitte gebe alle nötigen Informationen an, im die neue Benutzer:in zu erstellen.",
"please-provide-the-required-information-to-create-a-new-scanstation": "Bitte gebe alle für eine Scannerstation notwendigen Informationen an", "please-provide-the-required-information-to-create-a-new-scanstation": "Bitte gebe alle für eine Scannerstation notwendigen Informationen an",
"please-request-a-new-reset-mail": "Bitte eine neue Passwortreset-Mail anfordern...", "please-request-a-new-reset-mail": "Bitte eine neue Passwortreset-Mail anfordern...",
"please-wait-a-moment-your-login-is-still-being-processed": "Bitte warte einen Moment, deine Anmeldung wird verarbeitet",
"privacy": "Datenschutz", "privacy": "Datenschutz",
"privacy-loading": "Datenschutzerklärung lädt...", "privacy-loading": "Datenschutzerklärung lädt...",
"profile": "Profil", "profile": "Profil",
"profile-deleted": "Profil gelöscht!",
"profile-picture": "Profilbild", "profile-picture": "Profilbild",
"profile-updated": "Profil wurde aktualisiert!", "profile-updated": "Profil wurde aktualisiert!",
"read-license": "Lizenz-Text lesen", "read-license": "Lizenz-Text lesen",
@@ -312,6 +339,7 @@
"runner-import": "Läufer:innen Import", "runner-import": "Läufer:innen Import",
"runner-is-being-added": "Läufer:in wird hinzugefügt...", "runner-is-being-added": "Läufer:in wird hinzugefügt...",
"runner-updated": "Läufer:in aktualisiert!", "runner-updated": "Läufer:in aktualisiert!",
"runnercards": "Laeuferkarten",
"runnerimport_verify_runners_org": "Bitte die Läufer:innen für den Import in die Organisation \"{org_name}\" bestätigen", "runnerimport_verify_runners_org": "Bitte die Läufer:innen für den Import in die Organisation \"{org_name}\" bestätigen",
"runners": "Läufer", "runners": "Läufer",
"runners-are-being-imported": "Läufer:innen werden importiert ...", "runners-are-being-imported": "Läufer:innen werden importiert ...",
@@ -335,17 +363,23 @@
"search-for-runner-by-name-or-id": "Suche eine Läufer:in (via Name oder Id)", "search-for-runner-by-name-or-id": "Suche eine Läufer:in (via Name oder Id)",
"select-all": "Alle auswählen", "select-all": "Alle auswählen",
"select-language": "Sprache auswählen", "select-language": "Sprache auswählen",
"selfservice-registration": "Selfservice Registrierung",
"send-a-mail-to-lfk-odit-services": "Sende eine Mail an lfk@odit.services", "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", "set-the-user-active-inactive": "Den Benutzer auf (in)aktiv setzen",
"settings": "Einstellungen", "settings": "Einstellungen",
"settings-for-your-profile": "Die Einstellungen deines Accounts", "settings-for-your-profile": "Die Einstellungen deines Accounts",
"something-about-the-group": "Infos zur Gruppe", "something-about-the-group": "Infos zur Gruppe",
"sponsoring-quittungs-liste_herunterladen": "Sponsoring-Quittungs-Liste herunterladen",
"sponsorings": "Sponsoringerklaerungen",
"stats-are-being-loaded": "Die Statistiken werden geladen...", "stats-are-being-loaded": "Die Statistiken werden geladen...",
"status": "Status", "status": "Status",
"stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können", "stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können",
"successful-password-reset": "Passwort erfolgreich zurückgesetzt!", "successful-password-reset": "Passwort erfolgreich zurückgesetzt!",
"team": "Team", "team": "Team",
"team-added": "Team wurde hinzugefügt",
"team-deleted": "Team gelöscht",
"team-detail-is-being-loaded": "Team wird geladen...", "team-detail-is-being-loaded": "Team wird geladen...",
"team-is-being-added": "Team wird erstellt...",
"team-name": "Teamname", "team-name": "Teamname",
"team-name-is-required": "Teamname ist erforderlich", "team-name-is-required": "Teamname ist erforderlich",
"teams": "Teams", "teams": "Teams",
@@ -370,26 +404,35 @@
"total-distance": "gelaufene Strecke", "total-distance": "gelaufene Strecke",
"total-donation-amount": "Gesamtbetrag", "total-donation-amount": "Gesamtbetrag",
"total-donations": "Spendensumme", "total-donations": "Spendensumme",
"total-paid-amount": "Gezahlter Gesamtbetrag",
"total-scans": "gesamte Scans", "total-scans": "gesamte Scans",
"total_donation_amount_in_eur": "Gesamtbetrag in €",
"track": "Track", "track": "Track",
"track-added": "Track hinzugefügt", "track-added": "Track hinzugefügt",
"track-data-is-being-loaded": "Trackdaten werden geladen", "track-data-is-being-loaded": "Trackdaten werden geladen",
"track-is-being-added": "Track wird hinzugefügt...", "track-is-being-added": "Track wird hinzugefügt...",
"track-is-being-updated": "Track wird aktualisiert...",
"track-length-in-m": "Tracklänge (in Metern)", "track-length-in-m": "Tracklänge (in Metern)",
"track-length-must-be-greater-than-0": "Die Länge muss größer als 0 (Meter) sein", "track-length-must-be-greater-than-0": "Die Länge muss größer als 0 (Meter) sein",
"track-name": "Trackname", "track-name": "Trackname",
"track-name-must-not-be-empty": "Der Name muss angegeben werden", "track-name-must-not-be-empty": "Der Name muss angegeben werden",
"track-was-updated": "Track wurde aktualisiert",
"tracks": "Tracks", "tracks": "Tracks",
"unpaid": "Offen",
"update-card": "Karte aktualisieren",
"update-password": "Passwort ändern", "update-password": "Passwort ändern",
"updated-contact": "Kontakt aktualisiert!", "updated-contact": "Kontakt aktualisiert!",
"updated-donor": "Sponsor:in wurde aktualisiert", "updated-donor": "Sponsor:in wurde aktualisiert",
"updated-organization": "Organisation wurde aktualisiert", "updated-organization": "Organisation wurde aktualisiert",
"updated-scan": "Scan wurde aktualisiert", "updated-scan": "Scan wurde aktualisiert",
"updated-team": "Team wurde aktualisiert",
"updateing-group": "Gruppe wird aktualisiert...", "updateing-group": "Gruppe wird aktualisiert...",
"updating-card": "Karte wird aktualisiert", "updating-card": "Karte wird aktualisiert",
"updating-donation": "Sponsoring wird aktualisiert",
"updating-organization": "Organisation wird aktualisiert", "updating-organization": "Organisation wird aktualisiert",
"updating-permissions": "Berechtigungen werden aktualisiert...", "updating-permissions": "Berechtigungen werden aktualisiert...",
"updating-runner": "Läufer:in wird aktualisiert.", "updating-runner": "Läufer:in wird aktualisiert.",
"updating-team": "Team wird aktualisiert",
"updating-user": "Benutzer:in wird aktualisiert...", "updating-user": "Benutzer:in wird aktualisiert...",
"updating-your-profile": "Profil wird aktualisiert...", "updating-your-profile": "Profil wird aktualisiert...",
"user-added": "Benutzer hinzugefügt", "user-added": "Benutzer hinzugefügt",
@@ -407,10 +450,12 @@
"welcome_wavinghand": "Willkommen 👋", "welcome_wavinghand": "Willkommen 👋",
"yes-i-copied-the-token": "Ja, ich habe den Token kopiert", "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-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-enter-the-donations-paid-amount-manually-or-use-the-max-button-to-use-the-donations-exact-amount": "Du kannst den Betrag der Zahlung entweder manuell eingeben oder über den MAX Button auf den Spendenbetrag setzen",
"you-can-now-use-your-new-password-to-log-in-to-your-account": "Du kannst dich jetzt mit deinem neuen Passwort anmelden! 🎉", "you-can-now-use-your-new-password-to-log-in-to-your-account": "Du kannst dich jetzt mit deinem neuen Passwort anmelden! 🎉",
"you-can-provide-a-runner-but-you-dont-have-to": "Du kannst eine Läufer:in angeben, musst aber nicht.", "you-can-provide-a-runner-but-you-dont-have-to": "Du kannst eine Läufer:in angeben, musst aber nicht.",
"you-dont-have-any-scanstations-yet": "Es gibt noch keine Scannerstationen", "you-dont-have-any-scanstations-yet": "Es gibt noch keine Scannerstationen",
"you-have-to-provide-an-organization": "Du musst eine Organisation angeben", "you-have-to-provide-an-organization": "Du musst eine Organisation angeben",
"you-have-to-save-your-changes-to-generate-a-link": "Du musst deine Änderungen speichern, um einen Link zu generieren.",
"you-must-create-at-least-one-card-or-cancel": "Du musst mindestens eine Blankokarte erstellen (oder abbrechen).", "you-must-create-at-least-one-card-or-cancel": "Du musst mindestens eine Blankokarte erstellen (oder abbrechen).",
"zip-postal-code": "Postleitzahl" "zip-postal-code": "Postleitzahl"
} }

View File

@@ -6,7 +6,8 @@
"active": "Active", "active": "Active",
"add-card": "Add Card", "add-card": "Add Card",
"add-donation": "Add donation", "add-donation": "Add donation",
"add-donor": "add donor", "add-donor": "Add donor",
"add-or-update-a-payment": "Add or update a payment",
"add-scan": "Add scan", "add-scan": "Add scan",
"add-the-first-scanstation": "Add your first scanstation.", "add-the-first-scanstation": "Add your first scanstation.",
"add-user-group": "Add User Group", "add-user-group": "Add User Group",
@@ -22,6 +23,7 @@
"add-your-fist-donation": "Add your fist donation", "add-your-fist-donation": "Add your fist donation",
"add-your-fist-scan": "Add your fist scan", "add-your-fist-scan": "Add your fist scan",
"adding-card": "Adding Card", "adding-card": "Adding Card",
"adding-donation": "Adding donation...",
"adding-scan": "Adding Scan", "adding-scan": "Adding Scan",
"address": "Address", "address": "Address",
"address-is-required": "Address is required", "address-is-required": "Address is required",
@@ -30,6 +32,7 @@
"all-associated-donations-will-get-deleted-as-well": "All associated donations will get deleted as well", "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-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!", "all-associated-teams-and-runners-will-be-deleted-too": "All associated teams and runners will be deleted too!",
"already-paid": "Already paid",
"amount": "Amount", "amount": "Amount",
"amount-per-kilometer": "Amount per kilometer", "amount-per-kilometer": "Amount per kilometer",
"apartment-suite-etc": "Apartment, suite, etc.", "apartment-suite-etc": "Apartment, suite, etc.",
@@ -50,9 +53,11 @@
"card-deleted": "Card deleted", "card-deleted": "Card deleted",
"card-updated": "Card updated", "card-updated": "Card updated",
"cards": "Cards", "cards": "Cards",
"certificates": "Certificates",
"change-your-password-here": "Change your password here", "change-your-password-here": "Change your password here",
"changing-your-password": "Changing your password", "changing-your-password": "Changing your password",
"city": "City", "city": "City",
"click-to-copy-the-link-into-your-clipboard": "Click to copy the link into your clipboard",
"click-to-copy-token-to-clipboard": "Click to copy the token to your clipboard", "click-to-copy-token-to-clipboard": "Click to copy the token to your clipboard",
"close": "Close", "close": "Close",
"code": "Code", "code": "Code",
@@ -66,12 +71,15 @@
"confirm-deletion": "Confirm Deletion", "confirm-deletion": "Confirm Deletion",
"confirm-the-new-password": "Confirm the new password", "confirm-the-new-password": "Confirm the new password",
"contact": "Contact", "contact": "Contact",
"contact-added": "Contact added",
"contact-deleted": "Contact deleted", "contact-deleted": "Contact deleted",
"contact-information": "Contact Information", "contact-information": "Contact Information",
"contact-is-being-added": "Contact is being added...",
"contact-is-being-updated": "Contact is being updated...", "contact-is-being-updated": "Contact is being updated...",
"contact-is-not-a-member-in-any-group": "Contact is not a member in any group", "contact-is-not-a-member-in-any-group": "Contact is not a member in any group",
"contacts": "Contacts", "contacts": "Contacts",
"contacts-are-being-loaded": "contacts are being loaded...", "contacts-are-being-loaded": "contacts are being loaded...",
"copied-link-to-clipboard": "Copied link to clipboard",
"copied-token-to-clipboard": "Copied token to clipboard", "copied-token-to-clipboard": "Copied token to clipboard",
"count_organizations": "# Organizations", "count_organizations": "# Organizations",
"count_teams": "# Teams", "count_teams": "# Teams",
@@ -90,12 +98,14 @@
"create-a-new-track": "Create a new Track", "create-a-new-track": "Create a new Track",
"create-a-new-user": "Create a new User", "create-a-new-user": "Create a new User",
"create-a-new-user-group": "Create a new user group", "create-a-new-user-group": "Create a new user group",
"create-and-generate-pdf": "Create and generate PDF",
"create-bulk-blanco-cards": "Create bulk blanco cards", "create-bulk-blanco-cards": "Create bulk blanco cards",
"create-bulk-cards": "Add blanco cards", "create-bulk-cards": "Add blanco cards",
"create-organization": "Create Organization", "create-organization": "Create Organization",
"create-team": "Create Team", "create-team": "Create Team",
"create-track": "Create Track", "create-track": "Create Track",
"create-user": "Create User", "create-user": "Create User",
"create-without-pdf": "Create without PDF",
"created-blanco-cards": "Created blanco cards", "created-blanco-cards": "Created blanco cards",
"creating-blanco-cards": "Creating blanco cards", "creating-blanco-cards": "Creating blanco cards",
"credits": "Credits", "credits": "Credits",
@@ -152,6 +162,9 @@
"documentation": "Documentation", "documentation": "Documentation",
"donation-amount": "Donation amount", "donation-amount": "Donation amount",
"donation-amount-must-be-greater-that-0-00eur": "Donation amount must be greater that 0.00€", "donation-amount-must-be-greater-that-0-00eur": "Donation amount must be greater that 0.00€",
"donation-deleted": "Donation deleted",
"donation-updated": "Donation updated",
"donation_added": "Donation_added",
"donations": "Donations", "donations": "Donations",
"donor": "Donor", "donor": "Donor",
"donor-added": "Donor added", "donor-added": "Donor added",
@@ -171,6 +184,7 @@
"enabled": "enabled", "enabled": "enabled",
"enabled_large": "Enabled", "enabled_large": "Enabled",
"english": "English", "english": "English",
"enter-payment": "Enter payment",
"error-during-import": "Error during import", "error-during-import": "Error during import",
"error-whyile-copying-to-clipboard": "Error while copying to clipboard", "error-whyile-copying-to-clipboard": "Error while copying to clipboard",
"error_on_login": "Error on login", "error_on_login": "Error on login",
@@ -178,6 +192,7 @@
"everything-concerning-your-profile": "Everything concerning your profile", "everything-concerning-your-profile": "Everything concerning your profile",
"everything-is-more-fun-together": "everything is more fun together 🏃‍♂️🏃‍♀️🏃‍♂️", "everything-is-more-fun-together": "everything is more fun together 🏃‍♂️🏃‍♀️🏃‍♂️",
"faq": "FAQ", "faq": "FAQ",
"filename_sponsoringquittungsliste": "DonorReceiptList",
"filter-by-organization-team": "Filter by Organization/ Team", "filter-by-organization-team": "Filter by Organization/ Team",
"first-name": "First name", "first-name": "First name",
"first-name-is-required": "First Name is required", "first-name-is-required": "First Name is required",
@@ -187,6 +202,9 @@
"geerbte": "inherited", "geerbte": "inherited",
"general-stats": "General Stats", "general-stats": "General Stats",
"general_promise_error": "😢 Error", "general_promise_error": "😢 Error",
"generate-runner-certificate": "Generate runner certificate",
"generate-runner-certificates": "Generate runner certificates",
"generate-runnercards": "Generate Runnercards",
"generate-sponsoring-contract": "generate sponsoring contract", "generate-sponsoring-contract": "generate sponsoring contract",
"generate-sponsoring-contracts": "generate sponsoring contracts", "generate-sponsoring-contracts": "generate sponsoring contracts",
"generating-pdf": "generating PDF...", "generating-pdf": "generating PDF...",
@@ -242,6 +260,10 @@
"middle-name": "Middle name", "middle-name": "Middle name",
"minimum-lap-time-in-s": "minimum lap time in s", "minimum-lap-time-in-s": "minimum lap time in s",
"minimum-lap-time-must-be-a-positive-number-or-0": "minimum lap time must be a positive number or 0", "minimum-lap-time-must-be-a-positive-number-or-0": "minimum lap time must be a positive number or 0",
"must-be-at-least-10-characters-long": "Must be at least 10 characters long!",
"must-contain-a-lowercase-letter": "Must contain a lowercase letter!",
"must-contain-a-number": "Must contain a number!",
"must-contain-a-uppercase-letter": "Must contain a uppercase letter!",
"name": "Name", "name": "Name",
"name-is-required": "Name is required", "name-is-required": "Name is required",
"new-password": "New password", "new-password": "New password",
@@ -256,6 +278,7 @@
"no-runners-found": "No runners found", "no-runners-found": "No runners found",
"no-tracks-added-yet": "there are no tracks added yet.", "no-tracks-added-yet": "there are no tracks added yet.",
"non-blanko": "Non/Blanko", "non-blanko": "Non/Blanko",
"open": "OPEN",
"organization": "Organization", "organization": "Organization",
"organization-added": "Organization added", "organization-added": "Organization added",
"organization-deleted": "Organization deleted", "organization-deleted": "Organization deleted",
@@ -266,6 +289,8 @@
"organizations-are-being-loaded": "organizations are being loaded...", "organizations-are-being-loaded": "organizations are being loaded...",
"orgs": "Organizations", "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!", "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!",
"paid": "PAID",
"paid-amount": "Paid amount",
"password": "Password", "password": "Password",
"password-changed": "Password changed!", "password-changed": "Password changed!",
"password-is-required": "Password is required", "password-is-required": "Password is required",
@@ -273,7 +298,8 @@
"password-reset-in-progress": "Password Reset in Progress...", "password-reset-in-progress": "Password Reset in Progress...",
"password-reset-mail-sent": "Password reset mail was sent to \"{usersEmail}\".", "password-reset-mail-sent": "Password reset mail was sent to \"{usersEmail}\".",
"password-reset-successful": "Password Reset successful!", "password-reset-successful": "Password Reset successful!",
"passwords-dont-match": "Passwords don't match", "passwords-dont-match": "Passwords don't match!",
"payment-amount-must-be-greater-than-0-00eur": "Payment amount must be greater than 0.00€!",
"pdf-generation-failed": "PDF generation failed!", "pdf-generation-failed": "PDF generation failed!",
"pdf-successfully-generated": "PDF successfully generated!", "pdf-successfully-generated": "PDF successfully generated!",
"pdfs-successfully-generated": "PDFs successfully generated!", "pdfs-successfully-generated": "PDFs successfully generated!",
@@ -296,9 +322,11 @@
"please-provide-the-required-information-to-add-a-new-user": "Please provide the required information to add a new user.", "please-provide-the-required-information-to-add-a-new-user": "Please provide the required information to add a new user.",
"please-provide-the-required-information-to-create-a-new-scanstation": "Please provide the required information to create a new scanstation", "please-provide-the-required-information-to-create-a-new-scanstation": "Please provide the required information to create a new scanstation",
"please-request-a-new-reset-mail": "Please request a new reset mail...", "please-request-a-new-reset-mail": "Please request a new reset mail...",
"please-wait-a-moment-your-login-is-still-being-processed": "Please wait a moment, your login is still being processed",
"privacy": "Privacy", "privacy": "Privacy",
"privacy-loading": "Privacy loading...", "privacy-loading": "Privacy loading...",
"profile": "Profile", "profile": "Profile",
"profile-deleted": "Profile deleted!",
"profile-picture": "Profile Picture", "profile-picture": "Profile Picture",
"profile-updated": "Profile updated!", "profile-updated": "Profile updated!",
"read-license": "Read License", "read-license": "Read License",
@@ -312,6 +340,7 @@
"runner-import": "Runner Import", "runner-import": "Runner Import",
"runner-is-being-added": "Runner is being added...", "runner-is-being-added": "Runner is being added...",
"runner-updated": "Runner updated!", "runner-updated": "Runner updated!",
"runnercards": "Runnercards",
"runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"", "runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"",
"runners": "Runners", "runners": "Runners",
"runners-are-being-imported": "Runners are being imported...", "runners-are-being-imported": "Runners are being imported...",
@@ -335,17 +364,23 @@
"search-for-runner-by-name-or-id": "Search for runner (by name or id)", "search-for-runner-by-name-or-id": "Search for runner (by name or id)",
"select-all": "select all", "select-all": "select all",
"select-language": "Select language", "select-language": "Select language",
"selfservice-registration": "Selfservice registration",
"send-a-mail-to-lfk-odit-services": "send a mail to lfk@odit.services", "send-a-mail-to-lfk-odit-services": "send a mail to lfk@odit.services",
"set-the-user-active-inactive": "set the user active/ inactive", "set-the-user-active-inactive": "set the user active/ inactive",
"settings": "Settings", "settings": "Settings",
"settings-for-your-profile": "Settings for your profile", "settings-for-your-profile": "Settings for your profile",
"something-about-the-group": "Something about the group...", "something-about-the-group": "Something about the group...",
"sponsoring-quittungs-liste_herunterladen": "Download donor receipt list",
"sponsorings": "Sponsorings",
"stats-are-being-loaded": "stats are being loaded...", "stats-are-being-loaded": "stats are being loaded...",
"status": "Status", "status": "Status",
"stuff-that-could-harm-your-profile": "Stuff that could harm your profile", "stuff-that-could-harm-your-profile": "Stuff that could harm your profile",
"successful-password-reset": "Successful password reset!", "successful-password-reset": "Successful password reset!",
"team": "Team", "team": "Team",
"team-added": "Team added",
"team-deleted": "Team deleted",
"team-detail-is-being-loaded": "team detail is being loaded...", "team-detail-is-being-loaded": "team detail is being loaded...",
"team-is-being-added": "Team is being added...",
"team-name": "Team name", "team-name": "Team name",
"team-name-is-required": "team name is required", "team-name-is-required": "team name is required",
"teams": "Teams", "teams": "Teams",
@@ -370,27 +405,35 @@
"total-distance": "total distance", "total-distance": "total distance",
"total-donation-amount": "total donation amount", "total-donation-amount": "total donation amount",
"total-donations": "total donations", "total-donations": "total donations",
"total-paid-amount": "Total paid amount",
"total-scans": "total scans", "total-scans": "total scans",
"total_donation_amount_in_eur": "Total donation amount in €",
"track": "Track", "track": "Track",
"track-added": "Track added", "track-added": "Track added",
"track-data-is-being-loaded": "Track data is being loaded", "track-data-is-being-loaded": "Track data is being loaded",
"track-is-being-added": "Track is being added...", "track-is-being-added": "Track is being added...",
"track-is-being-updated": "Track is being updated...",
"track-length-in-m": "Track Length in m", "track-length-in-m": "Track Length in m",
"track-length-must-be-greater-than-0": "Track length must be greater than 0", "track-length-must-be-greater-than-0": "Track length must be greater than 0",
"track-name": "Track name", "track-name": "Track name",
"track-name-must-not-be-empty": "Track name must not be empty", "track-name-must-not-be-empty": "Track name must not be empty",
"track-was-updated": "Track was updated!",
"tracks": "Tracks", "tracks": "Tracks",
"unpaid": "Unpaid",
"update-card": "Update Card", "update-card": "Update Card",
"update-password": "Update password", "update-password": "Update password",
"updated-contact": "Updated contact!", "updated-contact": "Updated contact!",
"updated-donor": "updated donor", "updated-donor": "updated donor",
"updated-organization": "updated organization", "updated-organization": "updated organization",
"updated-scan": "updated scan", "updated-scan": "updated scan",
"updated-team": "Updated team",
"updateing-group": "updateing group...", "updateing-group": "updateing group...",
"updating-card": "Updating card", "updating-card": "Updating card",
"updating-donation": "Updating donation",
"updating-organization": "updating organization", "updating-organization": "updating organization",
"updating-permissions": "updating permissions...", "updating-permissions": "updating permissions...",
"updating-runner": "Updating runner...", "updating-runner": "Updating runner...",
"updating-team": "Updating team",
"updating-user": "updating user...", "updating-user": "updating user...",
"updating-your-profile": "Updating your profile...", "updating-your-profile": "Updating your profile...",
"user-added": "User added", "user-added": "User added",
@@ -408,10 +451,12 @@
"welcome_wavinghand": "Welcome 👋", "welcome_wavinghand": "Welcome 👋",
"yes-i-copied-the-token": "Yes, I copied the token", "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-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-enter-the-donations-paid-amount-manually-or-use-the-max-button-to-use-the-donations-exact-amount": "You can enter the donation's paid amount manually or use the MAX button to use the donation's exact amount.",
"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-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-can-provide-a-runner-but-you-dont-have-to": "You can provide a runner, but you don't have to.", "you-can-provide-a-runner-but-you-dont-have-to": "You can provide a runner, but you don't have to.",
"you-dont-have-any-scanstations-yet": "You don't have any scanstations yet", "you-dont-have-any-scanstations-yet": "You don't have any scanstations yet",
"you-have-to-provide-an-organization": "You have to provide an organization", "you-have-to-provide-an-organization": "You have to provide an organization",
"you-have-to-save-your-changes-to-generate-a-link": "You have to save your changes to generate a link.",
"you-must-create-at-least-one-card-or-cancel": "You must create at least one card (or cancel).", "you-must-create-at-least-one-card-or-cancel": "You must create at least one card (or cancel).",
"zip-postal-code": "ZIP/ postal code" "zip-postal-code": "ZIP/ postal code"
} }

9
src/main.js Normal file
View File

@@ -0,0 +1,9 @@
import './style.css';
import "toastify-js/src/toastify.css";
import "gridjs/dist/theme/mermaid.css";
import App from './App.svelte';
const app = new App({
target: document.body
});
export default app;

3
src/style.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,14 +0,0 @@
export const register = () => {
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').then(
(registration) => {
// console.log(`sw successful with scope: ${registration.scope}`);
},
(err) => {
// console.log(`sw failed: ${err}`);
}
);
});
}
};

View File

@@ -1,14 +1,15 @@
module.exports = { module.exports = {
purge: { mode: 'jit',
content: [ './src/**/*.svelte' ] purge: [ './src/**/*.svelte' ],
},
// darkMode: 'media',
variants: {},
plugins: [],
theme: { theme: {
container: { extend: {
center: true, colors: {
padding: '1.5rem' reepolee: {
500: '#b40000',
600: '#9c0000',
700: '#750000'
}
}
} }
} }
}; };

View File

@@ -1,19 +0,0 @@
const fs = require('fs');
let content_svelteconfig = fs.readFileSync('./s-config.template.js', { encoding: 'utf8' });
let content_html = fs.readFileSync('./index.template.html', { encoding: 'utf8' });
if (process.env.NODE_ENV_ODIT == 'development_fast') {
content_html = content_html.replace(
'__TAILWIND_INSERT__',
'<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.2/dist/tailwind.min.css">'
);
content_svelteconfig = content_svelteconfig.replace('__insert__', '{postcss:{}}');
} else {
content_html = content_html.replace('__TAILWIND_INSERT__', '');
content_svelteconfig = content_svelteconfig.replace(
'__insert__',
"{postcss:{plugins:[require('tailwindcss'),require('autoprefixer')]}}"
);
}
fs.writeFileSync('./public/index.html', content_html);
fs.writeFileSync('./svelte.config.js', content_svelteconfig);
console.info('dev setup script done');

View File

@@ -1,5 +1,5 @@
const fs = require('fs'); const fs = require('fs');
const package = JSON.parse(fs.readFileSync(`./package.json`, { encoding: 'utf-8' })); const package = JSON.parse(fs.readFileSync(`./package.json`, { encoding: 'utf-8' }));
const original = fs.readFileSync(`./index.template.html`, { encoding: 'utf-8' }); const original = fs.readFileSync(`./index.html`, { encoding: 'utf-8' });
let out = original.replace(/RELEASE_INFO-(\S)+-RELEASE_INFO/gi, 'RELEASE_INFO-' + package.version + '-RELEASE_INFO'); let out = original.replace(/RELEASE_INFO-(\S)+-RELEASE_INFO/gi, 'RELEASE_INFO-' + package.version + '-RELEASE_INFO');
fs.writeFileSync(`./index.template.html`, out); fs.writeFileSync(`./index.html`, out);

38
vite.config.js Normal file
View File

@@ -0,0 +1,38 @@
import svelte from '@sveltejs/vite-plugin-svelte';
import { minify } from 'html-minifier';
import { defineConfig } from 'vite';
//
const indexReplace = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
return minify(html, {
collapseWhitespace: true
});
}
};
};
export default defineConfig(({ command, mode }) => {
const isProduction = mode === 'production';
return {
// base: './',
build: {
polyfillDynamicImport: false,
cssCodeSplit: false,
minify: isProduction
},
plugins: [
svelte({
//@ts-ignore
hot: !isProduction,
emitCss: true,
extensions: [ '.md', '.svx', '.svelte' ],
preprocess: [
//
]
}),
indexReplace()
]
};
});

View File

@@ -1,9 +0,0 @@
module.exports = {
globDirectory: 'public',
globPatterns: [ '**/*.{js,ico,png,svg,html,webmanifest,txt,json}' ],
globIgnores: [ 'env.js', 'env.sample.js' ],
swDest: 'public/sw.js',
cleanupOutdatedCaches: true,
mode: 'production',
sourcemap: false
};