Compare commits

...

556 Commits
0.8.0 ... 1.3.0

Author SHA1 Message Date
e75be49be4 🚀RELEASE v1.3.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-19 17:41:40 +02:00
505fb8cb08 feat(donations): Implemented donation deletion via confirm modal 2023-04-19 17:41:21 +02:00
e5c9265588 feat(donations): Implemented add donation payment via datatable refresh 2023-04-19 17:34:34 +02:00
02003ec80e feat(donations): Donations reactive create and load into datatable 2023-04-19 17:21:24 +02:00
133470b6f2 feat(donationsoverview): Switched donations overview to datatable 2023-04-19 17:12:04 +02:00
4a6230c439 Merge branch 'dev' of git.odit.services:lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 16:56:23 +02:00
fdc7d80bbf 🚀RELEASE v1.2.0 2023-04-19 16:56:09 +02:00
352551e168 feat(donorsoverview): Dynamicly add newly generated donors 2023-04-19 16:55:14 +02:00
7aec050419 feat(donorsoverview): Implemented delete confirmation with datatable 2023-04-19 16:49:54 +02:00
4289034436 new license file version [CI SKIP] 2023-04-19 14:45:02 +00:00
8f8858f100 feat(DonationsOverview): i18n loading text
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-19 16:44:39 +02:00
d98fb0d5b2 feat(donoroverview): Added datatable formatters 2023-04-19 16:39:30 +02:00
5014bf5bc5 feat(donors): Load donors paginated 2023-04-19 16:23:00 +02:00
0708cabc75 🚀RELEASE v1.1.0
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 16:05:21 +02:00
4fcb26cf93 feat(dashboard): Updated stats icons 2023-04-19 16:04:02 +02:00
269def20d1 feat(dashboard): Added average sponsoring
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 15:59:45 +02:00
b8de9e0e42 feat(dashboard): Added average distance 2023-04-19 15:57:46 +02:00
7b2b598588 feat(dashboard): Added total donations 2023-04-19 15:56:21 +02:00
e0b61486b0 feat(dashboar): Added total donors to overview 2023-04-19 15:54:39 +02:00
3f86f7412f Lockfile 2023-04-19 15:53:33 +02:00
6454d960de Bumped client 2023-04-19 15:53:28 +02:00
37154c188b Merge branch 'dev' of git.odit.services:lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 15:38:15 +02:00
8da7578a0a 🚀RELEASE v1.0.0 2023-04-19 15:37:56 +02:00
2a64094006 new license file version [CI SKIP] 2023-04-19 13:37:32 +00:00
e9ce9644ff Merge pull request 'feature/175-request_pagination' (#176) from feature/175-request_pagination into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #176
2023-04-19 13:37:16 +00:00
52439aa5bc Allways set loaded to true
ref #175
2023-04-18 20:47:42 +02:00
ccf865687b Donation paginated loading
ref #175
2023-04-18 20:46:18 +02:00
cac34db1fd Paginated scan loading
ref #175
2023-04-18 20:43:04 +02:00
faf3893180 Paginated runner loading (1000 per page)
ref #175
2023-04-18 20:40:28 +02:00
c33dfcfddd Implemented Async loading of cards via pagination (500 cards per request)
ref #175
2023-04-18 20:38:01 +02:00
019e14ab1f Bumped lfk client
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-18 20:18:46 +02:00
b5790196c6 Merge branch 'dev' of git.odit.services:lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-17 17:56:42 +02:00
94a64ca690 🚀RELEASE v0.19.0 2023-04-17 17:56:34 +02:00
a6ce04c903 new license file version [CI SKIP] 2023-04-17 15:56:08 +00:00
165c154233 Merge pull request 'feature/173-scanstation_configcodes' (#174) from feature/173-scanstation_configcodes into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #174
2023-04-17 15:55:53 +00:00
318547db46 Adjusted size on smaller devices
ref #173
2023-04-17 17:55:33 +02:00
e60c09e19c I18n
ref #173
2023-04-17 17:52:05 +02:00
4834d1484c Styling
ref #173
2023-04-17 17:51:46 +02:00
4b6342727e Implemented config code generation
ref #173
2023-04-17 17:47:57 +02:00
cb5fa52cd9 Barcode placeholder
ref #173
2023-04-17 17:32:42 +02:00
947d01cf7f Lockfile 2023-04-17 17:32:31 +02:00
3563394fb3 Added bwip-js for barcode generation
ref #173
2023-04-17 17:32:16 +02:00
269d7a7def 🚀RELEASE v0.18.4
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 23:32:08 +02:00
e95f2333b0 Hide address2 in orgs by default 2023-04-15 23:30:46 +02:00
950217e0a3 🚀RELEASE v0.18.3
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 23:27:36 +02:00
5e65fb3301 Dont show adress 2 in runner overview 2023-04-15 23:27:19 +02:00
2a294cde04 🚀RELEASE v0.18.2
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-15 19:58:14 +02:00
e95420d79c Push in releaseit 2023-04-15 19:57:59 +02:00
cffbd17dc7 Added timestamps to scanoverview 2023-04-15 19:56:55 +02:00
00de8c3d75 🚀RELEASE v0.18.1
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 19:44:17 +02:00
1f4711d07a Merge branch 'dev' of git.odit.services:lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 19:43:12 +02:00
30e3396897 Missing scanstation translations 2023-04-15 19:43:06 +02:00
5291e049a1 fix(ConfirmStatsClientDeletion): ScanStationService -> StatsClientService
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 19:42:19 +02:00
08fbb504c9 fix(ConfirmScanStationDeletion): donorControllerRemove -> scanStationControllerRemove 2023-04-15 19:41:42 +02:00
e9ca1d3e5d Merge branch 'dev' of git.odit.services:lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-12 21:36:33 +02:00
eb80406fdb 🚀RELEASE v0.18.0 2023-04-12 21:36:26 +02:00
9fe53b0b9c fix: button onclick a11y
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-12 21:36:16 +02:00
9ae5e62e5d Module 2023-04-12 21:36:10 +02:00
1613ae7de6 Versionbuilder 2023-04-12 21:35:32 +02:00
ed1caa7be7 new license file version [CI SKIP] 2023-04-12 19:32:09 +00:00
d88f3a5a27 Merge pull request 'experiment/tanstack' (#172) from experiment/tanstack into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #172
2023-04-12 19:31:47 +00:00
c98eb49ae3 drop legacy dependencies 2023-04-12 21:31:25 +02:00
6d9d8a4724 Scans deletion 2023-04-12 21:25:33 +02:00
7d8c68a455 Scan deletion 2023-04-12 21:25:26 +02:00
8f33640bec Reactivated generate cards modal 2023-04-12 21:20:56 +02:00
f89023e24a Always load bulk created cards 2023-04-12 21:17:53 +02:00
f5d14f2e18 Scan reactive add 2023-04-12 21:14:37 +02:00
9811ede3b2 removed unused import 2023-04-12 21:11:56 +02:00
b2e51fea48 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 21:11:08 +02:00
2a915620c9 Import add to datatable 2023-04-12 21:11:06 +02:00
526688935f Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 21:10:51 +02:00
c7dcf7c66d scan delete working 2023-04-12 21:10:36 +02:00
06411dc147 Add runners reactivity 2023-04-12 21:09:40 +02:00
195d182cc9 Add Card appends current cards 2023-04-12 21:04:29 +02:00
17217dae76 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 20:54:33 +02:00
f105cc0a41 wip: delete scans 2023-04-12 20:54:09 +02:00
2f62c7ae89 Fixed killing of the dom 2023-04-12 20:54:00 +02:00
f6c1fea17c Added status filter function 2023-04-12 20:47:36 +02:00
178dc93319 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 20:43:09 +02:00
8ffe8eff06 Basic card delete modal 2023-04-12 20:43:08 +02:00
bd4952ee57 wip:ScanValid badge 2023-04-12 20:40:04 +02:00
4b171fd04f ScansOverview: use CardRunner link 2023-04-12 20:36:56 +02:00
2c198cfde8 Added custom filter to scan overview 2023-04-12 20:33:30 +02:00
7c6d39b5fa Added custom runner filter 2023-04-12 20:32:24 +02:00
9c13b2f9e9 fix(CardsOverview): table scroll + checkbox posititioning 2023-04-12 20:27:12 +02:00
1ec9556aa6 ScansOverview: fallback for manual scans 2023-04-12 20:22:59 +02:00
adec38b50b Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 20:20:31 +02:00
a35af6f020 Moved data loading to onmount 2023-04-12 20:20:30 +02:00
e74ff5e885 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 20:20:10 +02:00
c87561f63b wip: ScansOverview -> new datatable 2023-04-12 20:19:48 +02:00
c681570134 Fixed edit dispatch 2023-04-12 20:15:43 +02:00
53b945c72f Moved update card logic to overview 2023-04-12 20:14:04 +02:00
f6985daec7 Cards details modal 2023-04-12 20:07:46 +02:00
5662c3b6da Basic card table replace 2023-04-12 20:06:26 +02:00
9def0b27c9 fix: formatting 2023-04-12 19:56:29 +02:00
52a02c82d2 yeeted extra table styling 2023-04-12 19:47:48 +02:00
c241961d0a Extracted table header 2023-04-12 19:46:33 +02:00
8f50555a06 Extracted table bottom 2023-04-12 19:44:48 +02:00
b35375c929 fix min-w th 2023-04-12 19:43:15 +02:00
bf1e715261 RunnersOverview: table responsiveness 2023-04-12 19:41:41 +02:00
0240e1dca2 import 2023-04-12 19:36:35 +02:00
7cec2a00c5 Fixed id sorting 2023-04-12 19:34:54 +02:00
239f79fecb Moved code around 2023-04-12 19:28:10 +02:00
0265a59b82 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 19:26:56 +02:00
57dce34fc5 fix: table sort button + search style 2023-04-12 19:26:54 +02:00
d8110580e9 Added middle-name 2023-04-12 19:25:33 +02:00
03b7ada5ef Close modal on delete and import toastify 2023-04-12 19:23:27 +02:00
1df505ea00 fix(DeleteRunnerModal): ESC key 2023-04-12 19:22:52 +02:00
3e8dac3203 Reload table data on delete 2023-04-12 19:22:26 +02:00
95707a71a9 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 19:21:19 +02:00
fc2c2907c4 Disable sort for actions 2023-04-12 19:21:01 +02:00
ebdd1c2c0c quick cleanup 2023-04-12 19:20:03 +02:00
13254b24dd Fixed styling 2023-04-12 19:15:28 +02:00
e17eb64031 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 19:14:44 +02:00
a0727a0291 Extracted deletion into function of overview 2023-04-12 19:14:42 +02:00
f7f7926829 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 19:11:13 +02:00
9b7dca341b Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 19:11:08 +02:00
da3300562a Delete modal logic 2023-04-12 19:11:07 +02:00
e2ddb5a14c Added delete toast 2023-04-12 19:05:57 +02:00
fb9645aed6 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 19:05:02 +02:00
a4ebc7e126 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 19:04:22 +02:00
fd5db7d68a Added delete runner modal 2023-04-12 19:04:15 +02:00
e7eddb4f08 drop redundant button role 2023-04-12 19:03:49 +02:00
94155845f0 RunnersOverview: add filter keyboard support 2023-04-12 19:00:10 +02:00
3abf608b15 improved tablefilters/groupFilter 2023-04-12 18:56:55 +02:00
d31fe2363b ammendme 2023-04-12 18:51:48 +02:00
11a56f87e8 Implemented table buttons 2023-04-12 18:51:42 +02:00
19793cdcd4 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 18:47:52 +02:00
9363773fa1 Basic details and delete buttons 2023-04-12 18:47:47 +02:00
c7990882cf fix: TracksOverview 2023-04-12 18:46:31 +02:00
d4ab76ea1b RunnersOverview: pass selectedRunners to actions 2023-04-12 18:42:16 +02:00
2c992a0e63 RunnersOverview: disable debug log 2023-04-12 18:37:38 +02:00
88f96acc3c RunnersOverview: TopActionSection: add margin top 2023-04-12 18:37:26 +02:00
245db06173 updated default table row count 2023-04-12 18:37:02 +02:00
49c2cd5c4b show certificate, runnercard, sponsoring contract section 2023-04-12 18:36:49 +02:00
64db553185 fix: checkbox styling 2023-04-12 18:31:43 +02:00
a06a19ce9c Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 18:29:25 +02:00
592ddc1541 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 18:29:15 +02:00
cb5f2b73d0 drop old datatables 2023-04-12 18:29:06 +02:00
2304b12c1c Moved filter function to shared 2023-04-12 18:28:48 +02:00
38d3e1912c Unused filter function value 2023-04-12 18:26:36 +02:00
fbc14fd7b4 Make the linter happy 2023-04-12 16:31:56 +02:00
0283df22c8 Group filters 2023-04-12 16:30:00 +02:00
845737ee8e Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 16:18:55 +02:00
6993511c67 feat(RunnersOverview): row selection 2023-04-12 16:18:45 +02:00
9111ad147c Updated group name conversion 2023-04-12 16:14:06 +02:00
333214aa8f Added distance conversion 2023-04-12 16:09:57 +02:00
c0cde02fec Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 16:06:58 +02:00
070a20a2e5 Table header i18n 2023-04-12 16:06:55 +02:00
c5e8409079 add basic table styling 2023-04-12 16:05:42 +02:00
67eff0eda9 Dsable column filter for distance 2023-04-12 16:03:53 +02:00
3de7b632e0 Moved sort onclick to header only 2023-04-12 16:03:20 +02:00
842248e4c4 Added sort 2023-04-12 16:02:00 +02:00
c5d7ec25b5 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 15:58:33 +02:00
a9a965d698 Added filterfunctions 2023-04-12 15:58:31 +02:00
dc866dd540 default to 50 rows pagination 2023-04-12 15:58:13 +02:00
89252619b1 Yeeted old datatable references 2023-04-12 15:56:53 +02:00
2699b06d7c Filter id as equals 2023-04-12 15:54:36 +02:00
fd0d45f721 debug table 2023-04-12 15:52:19 +02:00
5ecf838dd2 cleanup headers 2023-04-12 15:52:13 +02:00
45a7a90cb8 pagination size 2023-04-12 15:52:05 +02:00
cac851f2b1 fix pagination next prev 2023-04-12 15:51:46 +02:00
238082b657 getPaginationRowModel 2023-04-12 15:48:08 +02:00
aecbabe522 -> onkeyup 2023-04-12 15:43:52 +02:00
a9cdac4f74 Added filter 2023-04-12 15:40:43 +02:00
a59dbbe50e wip: pagination 2023-04-12 15:36:45 +02:00
9bec95ede8 dependency update, drop focusTrap, first tanstack experiment in RunnersOverview 2023-04-12 15:20:38 +02:00
70307a9e82 Added delete cards button
All checks were successful
continuous-integration/drone/push Build is passing
ref #161
2023-04-12 14:34:06 +02:00
ef077b4e6a Cards deleted migrations
ref #161
2023-04-12 14:32:55 +02:00
dcabed4e93 Added translations
ref #161
2023-04-12 14:30:35 +02:00
1af047f66e Moved filter function to typed version
All checks were successful
continuous-integration/drone/push Build is passing
closes #171
2023-04-12 14:16:36 +02:00
ee91748b3c Bumped pnpm to 8
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-29 19:50:52 +02:00
e5241d619b No longer switching pnpm store path
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-29 19:48:02 +02:00
d79608edbb Switched drone to pnpm cache
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-29 19:46:49 +02:00
4cbd26580e Moved docker to pnpm with cache 2023-03-29 19:45:35 +02:00
fe62ad5539 ignore pnpm store from now on
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-29 19:44:06 +02:00
eb13f038a1 Removed pnpm store 2023-03-29 19:43:16 +02:00
9505c2b030 add TODO for ScansOverview status filter
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-19 12:45:07 +01:00
008835c24f ScansOverview: add ThFilterTrack
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-19 12:38:09 +01:00
7083b3d8d2 card/ThFilterRunner: text align left 2023-03-19 12:29:23 +01:00
754931b2f6 ScansOverview: migrate to datatable
All checks were successful
continuous-integration/drone/push Build is passing
close #168
2023-03-19 12:28:27 +01:00
2dc8ffba32 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 15:05:29 +01:00
d0fe6a2e85 new license file version [CI SKIP] 2023-03-15 14:05:18 +00:00
85705b6e68 🚀RELEASE v0.17.3 2023-03-15 15:05:14 +01:00
3ea7a015a9 dependency fixes
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 15:04:55 +01:00
44329413ed set pnpm to @7 2023-03-15 15:02:54 +01:00
46db68ab22 🚀RELEASE v0.17.2 2023-03-15 14:53:48 +01:00
dc9d7f22a2 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 14:47:32 +01:00
f917018fd9 improved ThFilterGroup style 2023-03-15 14:47:27 +01:00
7b420c430d 🚀RELEASE v0.17.1 2023-03-15 14:44:26 +01:00
00359d25c1 new license file version [CI SKIP] 2023-03-15 13:43:57 +00:00
d8a3063735 Revert "package dependency fixes, bumps, lockfile update"
Some checks failed
continuous-integration/drone/push Build is failing
This reverts commit 2c73b9862d.
2023-03-15 14:43:33 +01:00
6491af19e3 🚀RELEASE v0.17.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 13:35:48 +01:00
61328d20ed new license file version [CI SKIP] 2023-03-15 12:34:41 +00:00
0a6d92a1f3 Switched license generation to cache registry and pnpm
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 13:34:14 +01:00
3a576d1073 Pinned pnpm in dockerfile, thx @philipp
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 13:29:33 +01:00
b30b98b521 Pinned ci node version 2023-03-15 13:25:59 +01:00
43d82a2af0 Fixed pnpm being called without being installed
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 13:18:07 +01:00
6a4495b813 Merge pull request 'bugfix/162-create_card_modal' (#163) from bugfix/162-create_card_modal into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #163
2023-03-15 12:15:24 +00:00
e8a0ad6647 You can now create cards from runners by searching via #runnerid
ref #162
2023-03-15 13:12:42 +01:00
92b89cc4d8 Fixed double space in label
ref #162
2023-03-15 13:09:07 +01:00
268b1b1d98 Removed unused log
ref #162
2023-03-15 13:07:49 +01:00
75bc89ca30 Fixed runners not showing up
ref #162
2023-03-15 13:06:56 +01:00
0625937068 Merge pull request 'filter by runner full names + "#<ID>"' (#160) from feature/159-cardsoverview-filter-for-runner-full-names-and-id into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #160
2023-03-15 11:20:05 +00:00
32a9074963 Wow this api is fun
ref #159
2023-03-15 12:14:39 +01:00
b869b5fd2a remodelled for early return
ref #159
2023-03-15 11:39:51 +01:00
3a3e2f7157 add ThFilterRunner
ref #159
2023-03-15 11:10:08 +01:00
bea57aa03a fix styling for table filters th border
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 11:08:05 +01:00
30991d5364 UsersOverview: drop pfp
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:53:16 +01:00
5cc8b0811c UsersOverview: change profilepic scaling
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:51:40 +01:00
2c73b9862d package dependency fixes, bumps, lockfile update
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:51:23 +01:00
732b2f061e wip: pnpm + node version 2023-03-14 09:29:27 +01:00
3680533eef 🚀RELEASE v0.16.5
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-14 09:25:11 +01:00
1307d72c9d Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:24:54 +01:00
405dfa0c34 new license file version [CI SKIP] 2023-03-14 08:24:37 +00:00
5c2d154ad1 🚀RELEASE v0.16.4 2023-03-14 09:24:31 +01:00
f2bf8d9bac fix: OrgDetail: clicking on address will toggle selfservice
All checks were successful
continuous-integration/drone/push Build is passing
close #158
2023-03-14 09:23:55 +01:00
f9cfd6bd06 🚀RELEASE v0.16.3
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-23 14:01:35 +01:00
287f63fa52 Merge branch 'dev' of git.odit.services:lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-23 14:00:20 +01:00
5fe47634e8 Bumped vite build targets 2023-02-23 13:59:27 +01:00
a6590910cf new license file version [CI SKIP] 2023-02-23 12:54:38 +00:00
ad454c386c Merge pull request 'feature/156-pdf_names' (#157) from feature/156-pdf_names into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #157
2023-02-23 12:54:04 +00:00
0b2c296de0 Added ids for generated pdfs
ref #156
2023-02-23 09:59:07 +01:00
0e85940cba 🚀RELEASE v0.16.2
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-23 08:36:54 +01:00
8d479c32f8 Merge pull request 'feature/147-cardoverview_performance' (#153) from feature/147-cardoverview_performance into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #153
2023-02-23 07:34:46 +00:00
549785cf7d Merge pull request 'Fixed scanmodal' (#155) from bugfix/154-scan_select_runner_by_id into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #155
2023-02-23 07:34:39 +00:00
aafc4c8d62 Removed unused console log
ref #154
2023-02-22 18:39:41 +01:00
47dedbdc73 Fixed scanmodal
fixes #154
2023-02-22 18:38:21 +01:00
6fe134afc8 i18n import
ref #147
2023-02-22 18:33:42 +01:00
63a50f92e7 Merge branch 'feature/147-cardoverview_performance' of git.odit.services:lfk/frontend into feature/147-cardoverview_performance 2023-02-22 18:32:29 +01:00
ca6da15ef7 i18n
ref #147
2023-02-22 18:32:25 +01:00
8dfa19fa0f Fixed all filter
ref #147
2023-02-22 18:31:36 +01:00
0feee0ae2f Fixed edit update bug
ref #147
2023-02-22 18:06:53 +01:00
2a6a39916a rename: ThFilterGroup -> ThFilterStatus
ref #147
2023-02-19 15:14:09 +01:00
f0a2b2859f Added custom status filter 2023-02-18 19:27:13 +01:00
32ddb66fc8 Trigger edit modal 2023-02-18 19:24:28 +01:00
df63c2388d Added old formatting for runner and status
ref #147
2023-02-18 19:22:42 +01:00
757655ea63 Bsic datatable conversion
ref #147
2023-02-18 19:20:29 +01:00
329c1cc037 Merge pull request 'fix: RunnerDetail: set .phone to null if empty' (#152) from bugfix/151-runnerdetail--cannot-unset-phone-number into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #152
2023-02-18 16:37:56 +00:00
da6dd55d13 set .phone to null if empty 2023-02-18 17:30:01 +01:00
0e5490f1c8 new license file version [CI SKIP] 2023-02-18 16:18:28 +00:00
b82d638de1 Merge pull request 'feature/146-runner-table-performance-data-table' (#150) from feature/146-runner-table-performance-data-table into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #150
2023-02-18 16:17:51 +00:00
224034dcc6 fix: z-index on action buttons 2023-02-18 17:17:04 +01:00
026d3d41c1 new license file version [CI SKIP] 2023-02-18 16:13:06 +00:00
fd1a06b359 Merge pull request 'feature/148-dashboard_statscards' (#149) from feature/148-dashboard_statscards into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #149
2023-02-18 16:12:30 +00:00
452d010183 Updated breakpoints
ref #148
2023-02-18 16:27:58 +01:00
eb1c17e3ac Dasboard Cards redesign 2023-02-18 16:24:13 +01:00
a101873eb0 Tailwind bump
ref #148
2023-02-18 16:24:02 +01:00
3d2acb692a Fixed top checkbox state 2023-02-18 15:53:47 +01:00
0900c2691e Fixed checkbox show 2023-02-18 15:52:59 +01:00
1337676e08 Basic checkbox fix 2023-02-18 15:49:24 +01:00
2e075eafab RunnersOverview loading fix
ref #146
2023-02-16 15:43:11 +01:00
14d64b6070 add group filtering to table
ref #146
2023-02-16 15:05:41 +01:00
81b8fbf4e3 1st datatable try with @vincjo/datatables 2023-02-16 12:27:45 +01:00
24d074752f formatting 2023-02-16 11:48:30 +01:00
08047a9307 cleaned up table search 2023-02-16 11:47:59 +01:00
1b0cd5b90b improved runner search 2023-02-16 11:45:38 +01:00
65e8998894 set table-layout:fixed + display when loaded
ref #146
2023-02-16 11:26:37 +01:00
449948050b 🚀RELEASE v0.16.1
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-15 15:05:33 +01:00
cf97281592 fix: donor detail: sponsorings: unset middlename will show as "null"
All checks were successful
continuous-integration/drone/push Build is passing
close #145
2023-02-15 15:05:01 +01:00
75684efa1a 🚀RELEASE v0.16.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-03 14:07:27 +01:00
2c4f27a943 new license file version [CI SKIP] 2023-02-03 13:05:38 +00:00
53b7dec7cd Merge pull request 'feature/143-beamershow_clients' (#144) from feature/143-beamershow_clients into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #144
2023-02-03 13:05:01 +00:00
e0cbfb000b Tailwind bump 2023-02-02 17:27:18 +01:00
3a66f4c862 new license file version [CI SKIP] 2023-02-02 16:23:56 +00:00
85ceaa464f Merge branch 'dev' into feature/143-beamershow_clients 2023-02-02 17:23:33 +01:00
976755338b Updated docker base images
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-02 17:21:58 +01:00
1c980059cf Switched drone to kaniko 2023-02-02 17:20:01 +01:00
2d8c4c1698 Ignore pnpm lock 2023-02-02 17:19:51 +01:00
19a333d7bd Added missing translation
ref #143
2023-02-02 17:16:24 +01:00
96c55db63d Added translation
ref #143
2023-02-02 17:13:16 +01:00
fecb07ee37 Re-added copy modal
ref #143
2023-02-02 17:11:46 +01:00
e10c6480a5 Removed Key after creation
ref #143
2023-02-02 17:08:29 +01:00
f3cc07c009 Fixed imports and naming 2023-02-02 17:05:34 +01:00
068076dd47 Added Statsclients to sidebar
ref #143
2023-02-02 17:03:18 +01:00
02158605be Basic statsclient detail
ref #143
2023-02-02 17:00:49 +01:00
674e6a90ec Updated mounted variables 2023-02-02 16:54:37 +01:00
f679330466 Updated Add modal
ref #143
2023-02-02 16:51:39 +01:00
93fc7c2e83 Updated deletion modal
ref #143
2023-02-02 16:46:18 +01:00
f299617c60 First page for statsclients
ref #143
2023-02-02 16:42:17 +01:00
28cbc5b98c Bumped apiclient
ref #143
2023-02-02 16:21:11 +01:00
c28f1ee0bc Bumped apiclient
ref #143
2023-02-02 16:04:21 +01:00
cff112d705 Pinned versions
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-02 16:02:52 +01:00
9fc4ad63c4 🚀RELEASE v0.15.6
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-19 17:52:02 +02:00
97054a71c1 new license file version [CI SKIP] 2021-07-19 15:49:32 +00:00
2391668a25 Fixed donations getting reduced to the first one on certificates
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-19 17:47:27 +02:00
717d33547c 🚀RELEASE v0.15.5
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-05 17:23:43 +02:00
997be32679 Merge pull request 'Fixed kilometer conversion' (#142) from bugfix/141-runner_kilometers into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #142
2021-07-05 15:22:55 +00:00
134f00c40e Fixed kilometer conversion
ref #141
2021-07-05 17:15:50 +02:00
47c898bdfd Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-05 17:01:59 +02:00
e752ee12d1 new license file version [CI SKIP] 2021-07-05 15:01:42 +00:00
cc4515ff66 🚀RELEASE v0.15.4 2021-07-05 17:01:31 +02:00
f190292171 Merge pull request 'fix total donation sum in dashboard' (#140) from bugfix/139-total-donation-sum-is-wrong into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #140

close #139
2021-07-05 15:00:44 +00:00
b246f2b349 divide by 100 + toFixes(2)
ref #139
2021-07-05 13:30:17 +02:00
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
ec447e2e36 Merge branch 'dev' into feature/94-runnercard_mgnt 2021-03-25 20:30:31 +01:00
0af2647965 🚀RELEASE v0.8.7
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-25 20:30:06 +01:00
08442154f4 Fixed listen on wrong permission🐞
Some checks failed
continuous-integration/drone/push Build is failing
2021-03-25 20:29:33 +01:00
9f7d2234fb Formatting
ref #94
2021-03-25 20:22:01 +01:00
ddd82a71a7 Moved the pdf generation related componenets to their own folder
ref #94
2021-03-25 20:20:21 +01:00
014ba3bf67 Teams now use the new sponsoring contracts module
ref #94
2021-03-25 20:17:48 +01:00
c87321f804 Fixed org generation not hiding the generation toast
ref #94
2021-03-25 20:12:32 +01:00
8b451b3c67 Orgs now use the new sponsoring contracts module
ref #94
2021-03-25 20:09:43 +01:00
0cfc87fbe6 Moved contract generation to it's own component
ref #94
2021-03-25 20:06:35 +01:00
ae9673070c Now w/ working dialog🎉🎉🎉
ref #94
2021-03-25 19:14:15 +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
1a52aaf8d1 Moved modal import to overview for simplification
ref #94
2021-03-25 18:55:16 +01:00
d6c315ab8e Sorted translations 🌍
ref #94
2021-03-25 18:39:03 +01:00
983ce56048 Merge branch 'dev' into feature/94-runnercard_mgnt
# Conflicts:
#	src/locales/de.json
#	src/locales/en.json
2021-03-25 18:38:31 +01:00
de2fe0e9f1 Sorted translations
ref #94
2021-03-25 18:36:51 +01:00
c3c95bf291 🚀RELEASE v0.8.6
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-25 18:32:22 +01:00
d2050b5948 Merge pull request 'Know Production Bugs 🐞' (#109) from bugfix/107-prod_issues into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #109
2021-03-25 17:30:27 +00:00
6b92405bae Removed middlename search from all files that had it
ref #107
2021-03-25 18:19:44 +01:00
49e87ccb15 Now disabled search by middlename as a quick workaround 🐞
ref #107
2021-03-25 18:16:12 +01:00
50fffef13b Fixed conflicting css
ref #107
2021-03-25 18:06:28 +01:00
82b1811971 Sorted translations 👀
ref #107
2021-03-25 18:00:11 +01:00
aeadef60bb Fixed missing translations for scanstations🌍
ref #107
2021-03-25 17:59:50 +01:00
a1ab65a0e9 Sorted translations🌍
ref #107
2021-03-25 17:49:13 +01:00
17e0805fe6 Errors now toast errors
ref #107
2021-03-25 17:48:54 +01:00
ddd9c396b6 Fixed runner import getting triggered with invalid information
ref #107
2021-03-25 17:46:14 +01:00
ef49e507c1 Fixed outsideclick not clearing import modal🛠
ref #107
2021-03-25 17:34:25 +01:00
fbe74a5d80 Commented out the buggy runner search to prevent bad UX
ref #107
2021-03-25 17:31:53 +01:00
076893981f Fixed mail login bug🐞📧
ref #107
2021-03-25 17:27:38 +01:00
fac059f02c Now w/working editing
ref #94
2021-03-24 16:58:06 +01:00
0313f8cc49 Added runnercard detail/edit modal
ref #94
2021-03-24 16:43:05 +01:00
7ad6b73574 Implemented bulk creation
ref #94
2021-03-23 19:55:55 +01:00
3cd0468b19 Bumped lfk client lib version
ref #94
2021-03-23 18:57:13 +01:00
f46ccb610e Added bulk creation modal to cards view
ref #94
2021-03-23 18:41:00 +01:00
8a32569a3b Added bulk card creation modal
ref #94
2021-03-23 18:35:21 +01:00
535b23ae91 Implemented Add card modal
ref #94
2021-03-23 17:58:13 +01:00
4715978f81 Added message for missing runner/blanco card)
ref #94
2021-03-23 17:39:14 +01:00
a516aa7775 Formatting
ref #94
2021-03-23 17:34:25 +01:00
77e9c205f9 Now importing runner overview
ref #94
2021-03-23 17:34:01 +01:00
e852305400 Now routing the cards page
ref #94
2021-03-23 17:31:11 +01:00
c6a15264b3 Added basic card overview
ref #94
2021-03-23 17:29:21 +01:00
2d0beaaaad Added CardsEmptyState + Emtystate graphic
ref #94
2021-03-23 17:19:10 +01:00
5c5ef95d2b Added basic cards page
ref #94
2021-03-23 17:13:31 +01:00
e838e6f321 🚀RELEASE v0.8.5
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-20 18:47:28 +01:00
ca983c72d4 Merge branch 'dev' of git.odit.services:lfk/frontend into dev 2021-03-20 18:47:08 +01:00
91dd5256e9 Fixed dupliacate mutation 🐞 2021-03-20 18:47:06 +01:00
3d4dc2d72b 🚀RELEASE v0.8.4
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-20 18:31:46 +01:00
ba3471068a CONFIG: add 'demo' as default username/password 2021-03-20 18:31:31 +01:00
5a7b2cf886 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev 2021-03-20 18:27:18 +01:00
cc926e84fb CONFIG: default_username + default_password 2021-03-20 18:27:15 +01:00
fff16e6650 🚀RELEASE v0.8.3
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-20 17:51:24 +01:00
d6f6d10cb6 Sorted translation 🌍 2021-03-20 17:50:56 +01:00
1249904582 More small fixes 2021-03-20 17:50:38 +01:00
8e0437728b Smaller bugfixes 2021-03-20 17:41:44 +01:00
fb0c0718e4 🚀RELEASE v0.8.2
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-20 17:29:35 +01:00
aa8196db3a Now using env base url
Some checks failed
continuous-integration/drone/push Build is failing
2021-03-20 17:29:15 +01:00
b2223b5110 🚀RELEASE v0.8.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-20 17:08:08 +01:00
3837c5673a Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-20 17:04:43 +01:00
910a0860a0 CI - add build:tags pipeline 2021-03-20 17:04:29 +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
135 changed files with 13227 additions and 3838 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,2 @@
public/env.sample.js
public/workbox-*.js
public/workbox-*.js.map
.pnpm-store

View File

@@ -19,6 +19,13 @@ get:
path: odit-git-bot
name: sshkey
---
kind: secret
name: npm_url
get:
path: odit-npm-cache
name: url
---
kind: pipeline
type: kubernetes
@@ -27,10 +34,14 @@ name: build:dev
steps:
- name: run full license export
depends_on: ["clone"]
image: node:alpine
image: registry.odit.services/hub/library/node:19.7.0-alpine3.16
commands:
- yarn
- yarn licenses:export
- npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8
- pnpm i
- pnpm licenses:export
environment:
NPM_REGISTRY_URL:
from_secret: npm_url
- name: push new licenses file to repo
depends_on: ["run full license export"]
image: appleboy/drone-git-push
@@ -43,20 +54,48 @@ steps:
ssh_key:
from_secret: git_ssh
- name: build dev
image: plugins/docker
depends_on: [clone]
depends_on: ["clone"]
image: registry.odit.services/library/drone-kaniko
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: registry.odit.services/lfk/frontend
build_args:
- NPM_REGISTRY_URL:
from_secret: npm_url
repo: lfk/frontend
tags:
- dev
cache: true
registry: registry.odit.services
mtu: 1000
trigger:
branch:
- dev
event:
- push
- push
---
kind: pipeline
type: kubernetes
name: build:tags
steps:
- name: build $DRONE_TAG
depends_on: ["clone"]
image: registry.odit.services/library/drone-kaniko
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
build_args:
- NPM_REGISTRY_URL:
from_secret: npm_url
repo: lfk/frontend
tags:
- "${DRONE_TAG}"
cache: true
registry: registry.odit.services
trigger:
event:
- tag

9
.gitignore vendored
View File

@@ -1,11 +1,6 @@
node_modules
build
package-lock.json
yarn.lock
*.map
public/env.js
public/sw.js
public/index.html
public/workbox-*.js
svelte.config.js
public/index.html
/dist
.pnpm-store

View File

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

View File

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

View File

@@ -2,8 +2,752 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.3.0](https://git.odit.services/lfk/frontend/compare/1.2.0...1.3.0)
- feat(donations): Implemented donation deletion via confirm modal [`505fb8c`](https://git.odit.services/lfk/frontend/commit/505fb8cb08b81a7dcb08561bdda0f6464f140d3e)
- feat(donationsoverview): Switched donations overview to datatable [`133470b`](https://git.odit.services/lfk/frontend/commit/133470b6f2a63ec087f27c98ef260648a8672e5f)
- feat(donations): Implemented add donation payment via datatable refresh [`e5c9265`](https://git.odit.services/lfk/frontend/commit/e5c92655886ad9a6fcd7565fadd7955c477c3595)
- feat(donations): Donations reactive create and load into datatable [`02003ec`](https://git.odit.services/lfk/frontend/commit/02003ec80efc16aabd126710a6eeac18df43f841)
- feat(DonationsOverview): i18n loading text [`8f8858f`](https://git.odit.services/lfk/frontend/commit/8f8858f10071ddf9988d0ec0e3c4a891db24a102)
- new license file version [CI SKIP] [`4289034`](https://git.odit.services/lfk/frontend/commit/4289034436869750205a946247e7ab5f9892fe98)
#### [1.2.0](https://git.odit.services/lfk/frontend/compare/1.1.0...1.2.0)
> 19 April 2023
- feat(donoroverview): Added datatable formatters [`d98fb0d`](https://git.odit.services/lfk/frontend/commit/d98fb0d5b288c987a45ccbf2bb026ccaab539a71)
- 🚀RELEASE v1.2.0 [`fdc7d80`](https://git.odit.services/lfk/frontend/commit/fdc7d80bbf9bd698128e9ec4f91fa813499777a9)
- feat(donors): Load donors paginated [`5014bf5`](https://git.odit.services/lfk/frontend/commit/5014bf5bc5873cfe4ae04d71b4aff12b257dd2e3)
- feat(donorsoverview): Dynamicly add newly generated donors [`352551e`](https://git.odit.services/lfk/frontend/commit/352551e168b5dced5e7353e82655908d82d28af0)
- feat(donorsoverview): Implemented delete confirmation with datatable [`7aec050`](https://git.odit.services/lfk/frontend/commit/7aec050419f6f1bf853c3e1bc655b01725ed3b65)
#### [1.1.0](https://git.odit.services/lfk/frontend/compare/1.0.0...1.1.0)
> 19 April 2023
- 🚀RELEASE v1.1.0 [`0708cab`](https://git.odit.services/lfk/frontend/commit/0708cabc75e63a876e54a0b343318f8d934ae319)
- feat(dashboar): Added total donors to overview [`e0b6148`](https://git.odit.services/lfk/frontend/commit/e0b61486b089aa1e611ef3569b1521fc331ec0e4)
- feat(dashboard): Updated stats icons [`4fcb26c`](https://git.odit.services/lfk/frontend/commit/4fcb26cf9371e27e5d7e474b3558ef354e9114c0)
- feat(dashboard): Added average sponsoring [`269def2`](https://git.odit.services/lfk/frontend/commit/269def20d114ededaba3153bbd50ec2ddd70e1c6)
- feat(dashboard): Added total donations [`7b2b598`](https://git.odit.services/lfk/frontend/commit/7b2b59858839b98370af6fb1e6028ba0a1639186)
- feat(dashboard): Added average distance [`b8de9e0`](https://git.odit.services/lfk/frontend/commit/b8de9e0e427b3a8b56e6354ad7168ae12c7cce85)
- Lockfile [`3f86f74`](https://git.odit.services/lfk/frontend/commit/3f86f7412ffc4bc27328ad1f7d3c3118546e7e29)
- Bumped client [`6454d96`](https://git.odit.services/lfk/frontend/commit/6454d960de3f9f5ea86679f157b3b7e7cffde74d)
- new license file version [CI SKIP] [`2a64094`](https://git.odit.services/lfk/frontend/commit/2a640940062765a470387103a72ed14a2411d97b)
### [1.0.0](https://git.odit.services/lfk/frontend/compare/0.19.0...1.0.0)
> 19 April 2023
- 🚀RELEASE v1.0.0 [`8da7578`](https://git.odit.services/lfk/frontend/commit/8da7578a0a46a3e97d8c870e29399f6e8821c9fa)
- Merge pull request 'feature/175-request_pagination' (#176) from feature/175-request_pagination into dev [`e9ce964`](https://git.odit.services/lfk/frontend/commit/e9ce9644ff03f981cec6e9ad56aa5fdf0ff71ef4)
- Donation paginated loading [`ccf8656`](https://git.odit.services/lfk/frontend/commit/ccf865687b34016931a702c0a9b98a0a18e2b03a)
- Paginated scan loading [`cac34db`](https://git.odit.services/lfk/frontend/commit/cac34db1fd3bf5dc7c7be64b3a76ca4c8c77938d)
- Implemented Async loading of cards via pagination (500 cards per request) [`c33dfcf`](https://git.odit.services/lfk/frontend/commit/c33dfcfddddfed0902f3fa9b1d8a1d3e1560262f)
- Paginated runner loading (1000 per page) [`faf3893`](https://git.odit.services/lfk/frontend/commit/faf3893180bb735bea6f1ea58c896686b89949fe)
- Allways set loaded to true [`52439aa`](https://git.odit.services/lfk/frontend/commit/52439aa5bc8cfb1d78d5dfce55b1a0df640ad8f5)
- Bumped lfk client [`019e14a`](https://git.odit.services/lfk/frontend/commit/019e14ab1f99906f13d36c7148d0f4b7894072f2)
- new license file version [CI SKIP] [`a6ce04c`](https://git.odit.services/lfk/frontend/commit/a6ce04c90386f16abf235cc7b2f95aeea5011c7d)
#### [0.19.0](https://git.odit.services/lfk/frontend/compare/0.18.4...0.19.0)
> 17 April 2023
- 🚀RELEASE v0.19.0 [`94a64ca`](https://git.odit.services/lfk/frontend/commit/94a64ca69078c7fe2935eeb5f955fab95a79cb85)
- Merge pull request 'feature/173-scanstation_configcodes' (#174) from feature/173-scanstation_configcodes into dev [`165c154`](https://git.odit.services/lfk/frontend/commit/165c1542338c58f2abf42fef2e7b84b40d1e2d9c)
- I18n [`e60c09e`](https://git.odit.services/lfk/frontend/commit/e60c09e19c9cc20338906e84f4db4e009d926360)
- Implemented config code generation [`4b63427`](https://git.odit.services/lfk/frontend/commit/4b6342727ee0ea38597750d8c99edc301f1ccc2d)
- Styling [`4834d14`](https://git.odit.services/lfk/frontend/commit/4834d1484c3fb6ecd4a1b56aa9fbb8125c641a62)
- Adjusted size on smaller devices [`318547d`](https://git.odit.services/lfk/frontend/commit/318547db46045e41de64d5688368e85cd6fb8035)
- Lockfile [`947d01c`](https://git.odit.services/lfk/frontend/commit/947d01cf7fc7fe2ee88c56e017b0d663f1f3b4f9)
- Barcode placeholder [`cb5fa52`](https://git.odit.services/lfk/frontend/commit/cb5fa52cd9a97490b50fb0c02c26615b49650c08)
- Added bwip-js for barcode generation [`3563394`](https://git.odit.services/lfk/frontend/commit/3563394fb33d661890327e2ae08c400830b37844)
#### [0.18.4](https://git.odit.services/lfk/frontend/compare/0.18.3...0.18.4)
> 15 April 2023
- 🚀RELEASE v0.18.4 [`269d7a7`](https://git.odit.services/lfk/frontend/commit/269d7a7defdde059ef2bb5103262cf734e9babe9)
- Hide address2 in orgs by default [`e95f233`](https://git.odit.services/lfk/frontend/commit/e95f2333b0b958ed00c0e097b43aac2e70ad0d38)
#### [0.18.3](https://git.odit.services/lfk/frontend/compare/0.18.2...0.18.3)
> 15 April 2023
- 🚀RELEASE v0.18.3 [`950217e`](https://git.odit.services/lfk/frontend/commit/950217e0a350f9999b879475edf41f2f11c48179)
- Dont show adress 2 in runner overview [`5e65fb3`](https://git.odit.services/lfk/frontend/commit/5e65fb33013c3dad38e7ad6740b017ae206f278f)
#### [0.18.2](https://git.odit.services/lfk/frontend/compare/0.18.1...0.18.2)
> 15 April 2023
- 🚀RELEASE v0.18.2 [`2a294cd`](https://git.odit.services/lfk/frontend/commit/2a294cde040044bbebfb9c8b34b6c91b27772741)
- Added timestamps to scanoverview [`cffbd17`](https://git.odit.services/lfk/frontend/commit/cffbd17dc77054048cc9b14891f960f9a3fd18cb)
- Push in releaseit [`e95420d`](https://git.odit.services/lfk/frontend/commit/e95420d79c3227c0ca0cf0c0b599970c2b7d690e)
#### [0.18.1](https://git.odit.services/lfk/frontend/compare/0.18.0...0.18.1)
> 15 April 2023
- 🚀RELEASE v0.18.1 [`00de8c3`](https://git.odit.services/lfk/frontend/commit/00de8c3d75e90cd4614f42111f5f45bedde64130)
- Missing scanstation translations [`30e3396`](https://git.odit.services/lfk/frontend/commit/30e33968978bf33cedb31bcbf63fac273e1664f5)
- fix: button onclick a11y [`9fe53b0`](https://git.odit.services/lfk/frontend/commit/9fe53b0b9c71e8a6b4aa3f317327ffe729df0834)
- fix(ConfirmStatsClientDeletion): ScanStationService -&gt; StatsClientService [`5291e04`](https://git.odit.services/lfk/frontend/commit/5291e049a1d2e880fbe277095da91b70d4812c3f)
- fix(ConfirmScanStationDeletion): donorControllerRemove -&gt; scanStationControllerRemove [`08fbb50`](https://git.odit.services/lfk/frontend/commit/08fbb504c958415ce75e1e426296f870f0f1358d)
#### [0.18.0](https://git.odit.services/lfk/frontend/compare/0.17.3...0.18.0)
> 12 April 2023
- Moved filter function to typed version [`#171`](https://git.odit.services/lfk/frontend/issues/171)
- ScansOverview: migrate to datatable [`#168`](https://git.odit.services/lfk/frontend/issues/168)
- 🚀RELEASE v0.18.0 [`eb80406`](https://git.odit.services/lfk/frontend/commit/eb80406fdb8abf3f76bca742095e8f1f03480a56)
- wip: ScansOverview -&gt; new datatable [`c87561f`](https://git.odit.services/lfk/frontend/commit/c87561f63b90ab951daf91d9b8b54ba96a94cc7f)
- Basic card table replace [`5662c3b`](https://git.odit.services/lfk/frontend/commit/5662c3b6da67c00c94254bf39f8820e531fc93ef)
- RunnersOverview: table responsiveness [`bf1e715`](https://git.odit.services/lfk/frontend/commit/bf1e715261c0076fd8543dd1187c516209a73b16)
- Basic card delete modal [`8ffe8ef`](https://git.odit.services/lfk/frontend/commit/8ffe8eff0623767809cdc49ea15cf2d30b609285)
- wip: delete scans [`f105cc0`](https://git.odit.services/lfk/frontend/commit/f105cc0a41972261610d03015ecd5ee492dab8e2)
- Added delete runner modal [`fd5db7d`](https://git.odit.services/lfk/frontend/commit/fd5db7d68ab4b32f7bb14bd0275a447b749e1fd8)
- Extracted table bottom [`8f50555`](https://git.odit.services/lfk/frontend/commit/8f50555a06a35d0b11ac1d9201851d00d915c4b8)
- fix: formatting [`9def0b2`](https://git.odit.services/lfk/frontend/commit/9def0b27c9958c5c75466f56c17b6dc4d44d2b50)
- fix(CardsOverview): table scroll + checkbox posititioning [`9c13b2f`](https://git.odit.services/lfk/frontend/commit/9c13b2f9e938ad36cb74c66a8f91a5b872c8c2d1)
- Extracted table header [`c241961`](https://git.odit.services/lfk/frontend/commit/c241961d0ab0d6b8c3bf0453c1a21d84027ed62f)
- drop legacy dependencies [`c98eb49`](https://git.odit.services/lfk/frontend/commit/c98eb49ae31d1d28de79c7a9c945ade50828f07f)
- scan delete working [`c7dcf7c`](https://git.odit.services/lfk/frontend/commit/c7dcf7c66d7141117e08462ad05f6f441565d012)
- Add Card appends current cards [`195d182`](https://git.odit.services/lfk/frontend/commit/195d182cc977b4ac9f342f09a9ea69d461892a95)
- Moved data loading to onmount [`a35af6f`](https://git.odit.services/lfk/frontend/commit/a35af6f02055115d60d040baaf22dabbeda38498)
- Add runners reactivity [`06411dc`](https://git.odit.services/lfk/frontend/commit/06411dc14747fc9803da009a29688789c31dfc42)
- quick cleanup [`ebdd1c2`](https://git.odit.services/lfk/frontend/commit/ebdd1c2c0c2191c60dfacd0c4be989748628ca63)
- Extracted deletion into function of overview [`a0727a0`](https://git.odit.services/lfk/frontend/commit/a0727a02913708de06be8bf1b9f8cbae5489f080)
- Moved update card logic to overview [`53b945c`](https://git.odit.services/lfk/frontend/commit/53b945c72fa5b30f03eff8bb83f8f25d85d79b07)
- wip:ScanValid badge [`bd4952e`](https://git.odit.services/lfk/frontend/commit/bd4952ee575b4cd8caea3608b14d8c51b37a55eb)
- yeeted extra table styling [`52a02c8`](https://git.odit.services/lfk/frontend/commit/52a02c82d2bdabdf3e632910aa415c1ffb97fd7f)
- Added custom runner filter [`7c6d39b`](https://git.odit.services/lfk/frontend/commit/7c6d39b5fa60d1b834c9fb74dd26dfc2225161d6)
- Scan reactive add [`f5d14f2`](https://git.odit.services/lfk/frontend/commit/f5d14f2e1810462cba99b2482320746f0750d4a1)
- Moved code around [`239f79f`](https://git.odit.services/lfk/frontend/commit/239f79fecba732c807f4296cf524be15c1ccff1e)
- Scan deletion [`7d8c68a`](https://git.odit.services/lfk/frontend/commit/7d8c68a4550407e8c871545e7a0c5f622a1e3b9c)
- Added status filter function [`f6c1fea`](https://git.odit.services/lfk/frontend/commit/f6c1fea17ce64d890ac810d9dff058dd589c2989)
- Added delete toast [`e2ddb5a`](https://git.odit.services/lfk/frontend/commit/e2ddb5a14cc8b450c00129709bf5b933a47866dd)
- Fixed killing of the dom [`2f62c7a`](https://git.odit.services/lfk/frontend/commit/2f62c7ae89b7dda3bc0efd4d56e677898a3f197f)
- Cards details modal [`f6985da`](https://git.odit.services/lfk/frontend/commit/f6985daec71e4d922acd8a9b2673fd41317b075f)
- Reload table data on delete [`3e8dac3`](https://git.odit.services/lfk/frontend/commit/3e8dac3203f56723a3dad4a35887d60fc03d4ae3)
- Added middle-name [`d811058`](https://git.odit.services/lfk/frontend/commit/d8110580e9a33cfb0c6e9cbdce630262d5b5d4c1)
- Fixed id sorting [`7cec2a0`](https://git.odit.services/lfk/frontend/commit/7cec2a00c513678cae8643ef3905d566a538bde0)
- Import add to datatable [`2a91562`](https://git.odit.services/lfk/frontend/commit/2a915620c9adcb481be53e3c919bd5f14a5108ee)
- Scans deletion [`6d9d8a4`](https://git.odit.services/lfk/frontend/commit/6d9d8a4724135df98ed1bec74b0cc20a8bdbda53)
- fix(DeleteRunnerModal): ESC key [`1df505e`](https://git.odit.services/lfk/frontend/commit/1df505ea00968f65e7aff85baa80f8935fd2eb7b)
- Versionbuilder [`1613ae7`](https://git.odit.services/lfk/frontend/commit/1613ae7de6e84aa5f9df6774a8e820fcf515b14a)
- Fixed edit dispatch [`c681570`](https://git.odit.services/lfk/frontend/commit/c68157013431eeb1150e37fea87da02d5f7b032e)
- ScansOverview: use CardRunner link [`4b171fd`](https://git.odit.services/lfk/frontend/commit/4b171fd04f4fc895f9919294858e546686504c65)
- Added custom filter to scan overview [`2c198cf`](https://git.odit.services/lfk/frontend/commit/2c198cfde89363142c1c958e6961b7823196e04e)
- Disable sort for actions [`fc2c290`](https://git.odit.services/lfk/frontend/commit/fc2c2907c43e5b1fa192b571cf93b2f2e492158f)
- Delete modal logic [`da33005`](https://git.odit.services/lfk/frontend/commit/da3300562a25cf39945a94f873ec27f0d5fb9dc0)
- Module [`9ae5e62`](https://git.odit.services/lfk/frontend/commit/9ae5e62e5d84097082bc29fe20b329f6e614f6ed)
- new license file version [CI SKIP] [`ed1caa7`](https://git.odit.services/lfk/frontend/commit/ed1caa7be7673ef08f9c0074101f3cfb52963ab7)
- Merge pull request 'experiment/tanstack' (#172) from experiment/tanstack into dev [`d88f3a5`](https://git.odit.services/lfk/frontend/commit/d88f3a5a2737921536c5eb109734abafd7609e51)
- Removed pnpm store [`eb13f03`](https://git.odit.services/lfk/frontend/commit/eb13f038a1990ed2966552ab1d36df4a76e5a99a)
- dependency update, drop focusTrap, first tanstack experiment in RunnersOverview [`9bec95e`](https://git.odit.services/lfk/frontend/commit/9bec95ede80908e45ad0f18a66dbb45784f7e33e)
- Added translations [`dcabed4`](https://git.odit.services/lfk/frontend/commit/dcabed4e93134a1f04305c47536f1cc93b0cfb31)
- new license file version [CI SKIP] [`d0fe6a2`](https://git.odit.services/lfk/frontend/commit/d0fe6a2e8513638c4d8e2b3dafd48b91490eab3b)
- Bumped pnpm to 8 [`ee91748`](https://git.odit.services/lfk/frontend/commit/ee91748b3c7c2fb4f196d76121713ac42465b9dd)
- drop old datatables [`cb5f2b7`](https://git.odit.services/lfk/frontend/commit/cb5f2b73d0a5f71875f48d381382919f9bee364e)
- wip: pagination [`a59dbbe`](https://git.odit.services/lfk/frontend/commit/a59dbbe50ede47e0dada906d10887cc6b1ae3264)
- Added sort [`842248e`](https://git.odit.services/lfk/frontend/commit/842248e4c43bb94a0e73981d2d848e0723687932)
- cleanup headers [`5ecf838`](https://git.odit.services/lfk/frontend/commit/5ecf838dd231269c3c4f0d1e3376ff157457b04a)
- Moved filter function to shared [`2304b12`](https://git.odit.services/lfk/frontend/commit/2304b12c1cca6a64573223906ab0561ba9050ec5)
- Group filters [`0283df2`](https://git.odit.services/lfk/frontend/commit/0283df22c84740dd978e09120985215b65d7a12a)
- feat(RunnersOverview): row selection [`6993511`](https://git.odit.services/lfk/frontend/commit/6993511c67cc81303a1eece7fc2a4218dc818afc)
- Basic details and delete buttons [`9363773`](https://git.odit.services/lfk/frontend/commit/9363773fa1910975d48746572fe166363986c6ab)
- Added delete cards button [`70307a9`](https://git.odit.services/lfk/frontend/commit/70307a9e8272f22801c6765c947f8576cc1d1102)
- ScansOverview: add ThFilterTrack [`008835c`](https://git.odit.services/lfk/frontend/commit/008835c24f833aebbecc53921b0901a76f25bf06)
- Yeeted old datatable references [`8925261`](https://git.odit.services/lfk/frontend/commit/89252619b1f6d478c287178bfffc573cf574a8fa)
- Added filter [`a9cdac4`](https://git.odit.services/lfk/frontend/commit/a9cdac4f74a80afe3d0ce677ed5256201b75b29b)
- Moved sort onclick to header only [`3de7b63`](https://git.odit.services/lfk/frontend/commit/3de7b632e058e9ca9ab951de9e86cc2a40eceb68)
- Switched drone to pnpm cache [`d79608e`](https://git.odit.services/lfk/frontend/commit/d79608edbb7ca68b0a26f6c021a141373308081f)
- pagination size [`45a7a90`](https://git.odit.services/lfk/frontend/commit/45a7a90cb8ee933d064c5891b3532cb1ece38d38)
- RunnersOverview: add filter keyboard support [`9415584`](https://git.odit.services/lfk/frontend/commit/94155845f020679e8765d198b5ab4735e1d1142c)
- RunnersOverview: pass selectedRunners to actions [`d4ab76e`](https://git.odit.services/lfk/frontend/commit/d4ab76ea1be1789aaa53ca57d33b2356c6804ffd)
- fix pagination next prev [`cac851f`](https://git.odit.services/lfk/frontend/commit/cac851f2b19dafa01b08d1da612e34cbe3eb4133)
- Moved docker to pnpm with cache [`4cbd265`](https://git.odit.services/lfk/frontend/commit/4cbd26580e51b854fdfd369056f24a30b04100a1)
- fix: TracksOverview [`c799088`](https://git.odit.services/lfk/frontend/commit/c7990882cfa4ac6c3a2ffa696dcbf3b62790c1fe)
- Table header i18n [`070a20a`](https://git.odit.services/lfk/frontend/commit/070a20a2e599e9f00b1bd0e944f9d68d08dc1cba)
- default to 50 rows pagination [`dc866dd`](https://git.odit.services/lfk/frontend/commit/dc866dd5403ae681193c5e325b1f6d4068f80b61)
- improved tablefilters/groupFilter [`3abf608`](https://git.odit.services/lfk/frontend/commit/3abf608b15fcfc2cf65b705d28d394917e7ac333)
- Updated group name conversion [`9111ad1`](https://git.odit.services/lfk/frontend/commit/9111ad147c7537036de834f80db6e557d8d6f0d9)
- show certificate, runnercard, sponsoring contract section [`49c2cd5`](https://git.odit.services/lfk/frontend/commit/49c2cd5c4b32e1b75fe0dae48c1b470608936704)
- add basic table styling [`c5e8409`](https://git.odit.services/lfk/frontend/commit/c5e84090795636c7a80d6cabe46168e436d40f50)
- Cards deleted migrations [`ef077b4`](https://git.odit.services/lfk/frontend/commit/ef077b4e6ad722e6d1c9efd5060165f649fd94c9)
- ignore pnpm store from now on [`fe62ad5`](https://git.odit.services/lfk/frontend/commit/fe62ad5539bc94b242c30c2952bf4c115cc9abfd)
- ammendme [`d31fe23`](https://git.odit.services/lfk/frontend/commit/d31fe2363b0f167b937f7d67908e75c709b324d8)
- updated default table row count [`245db06`](https://git.odit.services/lfk/frontend/commit/245db0617306bab23993caf00dc5ba0405e95625)
- fix: checkbox styling [`64db553`](https://git.odit.services/lfk/frontend/commit/64db5531858b2275d27c53a347da6c0416a278e8)
- Added distance conversion [`333214a`](https://git.odit.services/lfk/frontend/commit/333214aa8f945975ee2dcbd1cfa285e5e75c9d02)
- Added filterfunctions [`a9a965d`](https://git.odit.services/lfk/frontend/commit/a9a965d6981b07b02e3f752e65e1e5c4e9df7147)
- card/ThFilterRunner: text align left [`7083b3d`](https://git.odit.services/lfk/frontend/commit/7083b3d8d2c81fa4ab14f21d379befbb2dab5b6c)
- import [`0240e1d`](https://git.odit.services/lfk/frontend/commit/0240e1dca2e162b8c79f294f5c651b4a38455235)
- fix: table sort button + search style [`57dce34`](https://git.odit.services/lfk/frontend/commit/57dce34fc5b04c297c5341db24ec47c2f1d39595)
- No longer switching pnpm store path [`e5241d6`](https://git.odit.services/lfk/frontend/commit/e5241d619beda90d271485b6c09142b1e7b9b5f1)
- Reactivated generate cards modal [`8f33640`](https://git.odit.services/lfk/frontend/commit/8f33640bec8f79987b1319fb0765535fe493d496)
- Always load bulk created cards [`f89023e`](https://git.odit.services/lfk/frontend/commit/f89023e24ab30c90a8cacf2e9590d1dfa1a743d5)
- ScansOverview: fallback for manual scans [`1ec9556`](https://git.odit.services/lfk/frontend/commit/1ec9556aa64b996d0bdd9cd3ee50a767a154f5f6)
- fix min-w th [`b35375c`](https://git.odit.services/lfk/frontend/commit/b35375c929ad8d525b2761c321ecef0399917828)
- Close modal on delete and import toastify [`03b7ada`](https://git.odit.services/lfk/frontend/commit/03b7ada5ef3cbf64f178d3d0d88c448d76a42885)
- Fixed styling [`13254b2`](https://git.odit.services/lfk/frontend/commit/13254b24dd75ca3eb47fd53d5368f047545e8420)
- Implemented table buttons [`11a56f8`](https://git.odit.services/lfk/frontend/commit/11a56f87e89866bd9b2f7029ef683fd139d60aa4)
- RunnersOverview: disable debug log [`2c992a0`](https://git.odit.services/lfk/frontend/commit/2c992a0e63fa72055d28785fe7f5c21fd26f1fea)
- RunnersOverview: TopActionSection: add margin top [`88f96ac`](https://git.odit.services/lfk/frontend/commit/88f96acc3c31c748d3da29b4564dd681a89208b9)
- Unused filter function value [`38d3e19`](https://git.odit.services/lfk/frontend/commit/38d3e1912cd6d5362db236c34e0c602b52060b73)
- Make the linter happy [`fbc14fd`](https://git.odit.services/lfk/frontend/commit/fbc14fd7b47009bf64a8824e7b60373eea031e3f)
- Dsable column filter for distance [`67eff0e`](https://git.odit.services/lfk/frontend/commit/67eff0eda9b7d49d1abe784247afdbfbd7a76b00)
- getPaginationRowModel [`238082b`](https://git.odit.services/lfk/frontend/commit/238082b657998f513111e832556926decbe84e21)
- -&gt; onkeyup [`aecbabe`](https://git.odit.services/lfk/frontend/commit/aecbabe52221c0ab8d94a42dc8ac42f4f397fde8)
- removed unused import [`9811ede`](https://git.odit.services/lfk/frontend/commit/9811ede3b23a488a49962a43dae5abad60f93341)
- drop redundant button role [`e7eddb4`](https://git.odit.services/lfk/frontend/commit/e7eddb4f08632cc803b8752e3561319fab538843)
- Filter id as equals [`2699b06`](https://git.odit.services/lfk/frontend/commit/2699b06d7c42608b1b3ce2037908a78143038441)
- debug table [`fd0d45f`](https://git.odit.services/lfk/frontend/commit/fd0d45f721396787ad35fca2a7fd0f26d98b23e9)
- add TODO for ScansOverview status filter [`9505c2b`](https://git.odit.services/lfk/frontend/commit/9505c2b03005f4e667082cb4527fea863dbbf7fc)
#### [0.17.3](https://git.odit.services/lfk/frontend/compare/0.17.2...0.17.3)
> 15 March 2023
- dependency fixes [`3ea7a01`](https://git.odit.services/lfk/frontend/commit/3ea7a015a9beba3c2e4d3eb966f24ff6d4ac786e)
- 🚀RELEASE v0.17.3 [`85705b6`](https://git.odit.services/lfk/frontend/commit/85705b6e684d0c41d3dc770cef7bffb199101576)
- set pnpm to @7 [`4432941`](https://git.odit.services/lfk/frontend/commit/44329413ed2ca23f74e86db041b2c25b2b1c2a2b)
#### [0.17.2](https://git.odit.services/lfk/frontend/compare/0.17.1...0.17.2)
> 15 March 2023
- new license file version [CI SKIP] [`00359d2`](https://git.odit.services/lfk/frontend/commit/00359d25c1bd3efdd6365bf284b3c07634049399)
- 🚀RELEASE v0.17.2 [`46db68a`](https://git.odit.services/lfk/frontend/commit/46db68ab229dc740dfff8835ef916f2c2e629b27)
- improved ThFilterGroup style [`f917018`](https://git.odit.services/lfk/frontend/commit/f917018fd92a8a5b034f735ac8b6e41995044317)
#### [0.17.1](https://git.odit.services/lfk/frontend/compare/0.17.0...0.17.1)
> 15 March 2023
- Revert "package dependency fixes, bumps, lockfile update" [`d8a3063`](https://git.odit.services/lfk/frontend/commit/d8a30637351e164599e07a6473d9a1d2b08d245d)
- 🚀RELEASE v0.17.1 [`7b420c4`](https://git.odit.services/lfk/frontend/commit/7b420c430d27bf0fc85a4297780164a593fc1be3)
#### [0.17.0](https://git.odit.services/lfk/frontend/compare/0.16.5...0.17.0)
> 15 March 2023
- new license file version [CI SKIP] [`61328d2`](https://git.odit.services/lfk/frontend/commit/61328d20ed2cfd1df7d3c32767f9c64154879d6d)
- wip: pnpm + node version [`732b2f0`](https://git.odit.services/lfk/frontend/commit/732b2f061e465bd08cf34c58d8cd6b021ba25dce)
- package dependency fixes, bumps, lockfile update [`2c73b98`](https://git.odit.services/lfk/frontend/commit/2c73b9862d401dac15021eed3f7847d46132a8ed)
- Fixed runners not showing up [`75bc89c`](https://git.odit.services/lfk/frontend/commit/75bc89ca3020c48f490c7602374616bd9461e78f)
- add ThFilterRunner [`3a3e2f7`](https://git.odit.services/lfk/frontend/commit/3a3e2f71575d3a0e39a5e13b05cff932b8683ac9)
- fix styling for table filters th border [`bea57aa`](https://git.odit.services/lfk/frontend/commit/bea57aa03acaaaa4b1860b30228dd5d1298317f8)
- You can now create cards from runners by searching via #runnerid [`e8a0ad6`](https://git.odit.services/lfk/frontend/commit/e8a0ad6647ab39252865f62b755f27c34ac2d243)
- remodelled for early return [`b869b5f`](https://git.odit.services/lfk/frontend/commit/b869b5fd2a01955fb21f936fa38eb5a9648e7de3)
- 🚀RELEASE v0.17.0 [`6491af1`](https://git.odit.services/lfk/frontend/commit/6491af19e375cbeba7ddd94e463b4dfe308a70a8)
- Wow this api is fun [`32a9074`](https://git.odit.services/lfk/frontend/commit/32a9074963cce3328f14b1f981ddd5ee49df0008)
- Fixed double space in label [`92b89cc`](https://git.odit.services/lfk/frontend/commit/92b89cc4d88c9d5625c2ddf7c81c98494f7f5271)
- UsersOverview: drop pfp [`30991d5`](https://git.odit.services/lfk/frontend/commit/30991d5364a09d517b2115a7e9ea3fbf1fe2e57d)
- Switched license generation to cache registry and pnpm [`0a6d92a`](https://git.odit.services/lfk/frontend/commit/0a6d92a1f3c5562f11562c433b3a04e3eaae3da4)
- Pinned pnpm in dockerfile, thx @philipp [`3a576d1`](https://git.odit.services/lfk/frontend/commit/3a576d1073ee503b68100e01054a1756bab62805)
- Pinned ci node version [`b30b98b`](https://git.odit.services/lfk/frontend/commit/b30b98b521eda2bc7fc055097546f716e90d92ef)
- Fixed pnpm being called without being installed [`43d82a2`](https://git.odit.services/lfk/frontend/commit/43d82a2af04af49c2169f78a0d0f27ef7e4d7558)
- Merge pull request 'bugfix/162-create_card_modal' (#163) from bugfix/162-create_card_modal into dev [`6a4495b`](https://git.odit.services/lfk/frontend/commit/6a4495b8131a31cd48a608c2275e80494d0a0fb4)
- Removed unused log [`268b1b1`](https://git.odit.services/lfk/frontend/commit/268b1b1d9830de196d1d95345d7a2467bbf19eb6)
- Merge pull request 'filter by runner full names + "#&lt;ID&gt;"' (#160) from feature/159-cardsoverview-filter-for-runner-full-names-and-id into dev [`0625937`](https://git.odit.services/lfk/frontend/commit/0625937068f0786078ffd29b9c8bb54949350b6c)
- UsersOverview: change profilepic scaling [`5cc8b08`](https://git.odit.services/lfk/frontend/commit/5cc8b0811cf290f97a4399b23c5ea4d961a5a91c)
#### [0.16.5](https://git.odit.services/lfk/frontend/compare/0.16.4...0.16.5)
> 14 March 2023
- 🚀RELEASE v0.16.5 [`3680533`](https://git.odit.services/lfk/frontend/commit/3680533eefef042fc77246dd3d374aafe10c428f)
- new license file version [CI SKIP] [`405dfa0`](https://git.odit.services/lfk/frontend/commit/405dfa0c34ba87fc450c22e0e9974f92c4cdeffe)
#### [0.16.4](https://git.odit.services/lfk/frontend/compare/0.16.3...0.16.4)
> 14 March 2023
- fix: OrgDetail: clicking on address will toggle selfservice [`#158`](https://git.odit.services/lfk/frontend/issues/158)
- 🚀RELEASE v0.16.4 [`5c2d154`](https://git.odit.services/lfk/frontend/commit/5c2d154ad180ce7916605871c63e2f5ac4428250)
#### [0.16.3](https://git.odit.services/lfk/frontend/compare/0.16.2...0.16.3)
> 23 February 2023
- 🚀RELEASE v0.16.3 [`f9cfd6b`](https://git.odit.services/lfk/frontend/commit/f9cfd6bd063b01a584774854d8fb5eab96f99528)
- Bumped vite build targets [`5fe4763`](https://git.odit.services/lfk/frontend/commit/5fe47634e8980e77b65c05f213c475cf49273609)
- new license file version [CI SKIP] [`a659091`](https://git.odit.services/lfk/frontend/commit/a6590910cfdc5e91fba91c1bc9237e407ef15fd2)
- Merge pull request 'feature/156-pdf_names' (#157) from feature/156-pdf_names into dev [`ad454c3`](https://git.odit.services/lfk/frontend/commit/ad454c386cbf11abc59d41d269d1a0ef7442c9ed)
- Added ids for generated pdfs [`0b2c296`](https://git.odit.services/lfk/frontend/commit/0b2c296de069a4ef302c5535de01bc18236675bc)
#### [0.16.2](https://git.odit.services/lfk/frontend/compare/0.16.1...0.16.2)
> 23 February 2023
- Fixed scanmodal [`#154`](https://git.odit.services/lfk/frontend/issues/154)
- 🚀RELEASE v0.16.2 [`0e85940`](https://git.odit.services/lfk/frontend/commit/0e85940cba292cbccd1ec038aa24f4a719382c19)
- Merge pull request 'feature/147-cardoverview_performance' (#153) from feature/147-cardoverview_performance into dev [`8d479c3`](https://git.odit.services/lfk/frontend/commit/8d479c32f82938904aee6a10deb80fea85487e4b)
- Merge pull request 'Fixed scanmodal' (#155) from bugfix/154-scan_select_runner_by_id into dev [`549785c`](https://git.odit.services/lfk/frontend/commit/549785cf7d1c9edc1be37dc745b97096232a08ca)
- i18n [`ca6da15`](https://git.odit.services/lfk/frontend/commit/ca6da15ef761184a55b18d56f749f660a32cbb83)
- Bsic datatable conversion [`757655e`](https://git.odit.services/lfk/frontend/commit/757655ea63b3667bc4612ae1595eb52910b61137)
- 1st datatable try with @vincjo/datatables [`81b8fbf`](https://git.odit.services/lfk/frontend/commit/81b8fbf4e341e6f2998a6e9e2053972c5c225021)
- Dasboard Cards redesign [`eb1c17e`](https://git.odit.services/lfk/frontend/commit/eb1c17e3ac7e8f5e7310a90421fc9db3ed15c497)
- set .phone to null if empty [`da6dd55`](https://git.odit.services/lfk/frontend/commit/da6dd55d139a672fa50204eabdca67d9740614a0)
- add group filtering to table [`14d64b6`](https://git.odit.services/lfk/frontend/commit/14d64b6070d98e6368da5709e9ff8221e8a621c7)
- formatting [`24d0747`](https://git.odit.services/lfk/frontend/commit/24d074752f1c5dc1a14b075ac14b448d7e129376)
- RunnersOverview loading fix [`2e075ea`](https://git.odit.services/lfk/frontend/commit/2e075eafab5c4d78fd9aa9d66834b477b2685bfc)
- Added old formatting for runner and status [`df63c23`](https://git.odit.services/lfk/frontend/commit/df63c2388da359dec9ed9968bc9f970be7092a45)
- Added custom status filter [`f0a2b28`](https://git.odit.services/lfk/frontend/commit/f0a2b2859fa18426a454b7d9d6dd22dfdfe7ce27)
- Basic checkbox fix [`1337676`](https://git.odit.services/lfk/frontend/commit/1337676e0894c46da0b6dcb7553e5ea8f88d0c14)
- Fixed all filter [`8dfa19f`](https://git.odit.services/lfk/frontend/commit/8dfa19fa0f9897c61342ece956df88731c7aa861)
- improved runner search [`1b0cd5b`](https://git.odit.services/lfk/frontend/commit/1b0cd5b90bcceb92627c6b7cdcdd7792ed81b50f)
- set table-layout:fixed + display when loaded [`65e8998`](https://git.odit.services/lfk/frontend/commit/65e89988943807c1606a8b6aea49564b13d52537)
- Trigger edit modal [`32ddb66`](https://git.odit.services/lfk/frontend/commit/32ddb66fc8d8cd689f1104759812f4cee4b7a613)
- cleaned up table search [`08047a9`](https://git.odit.services/lfk/frontend/commit/08047a93073c32f5dd7a8e958400ae8a5b7f4035)
- Fixed edit update bug [`0feee0a`](https://git.odit.services/lfk/frontend/commit/0feee0ae2fb6d8dba0b6fd72cedc0712dc749511)
- fix: z-index on action buttons [`224034d`](https://git.odit.services/lfk/frontend/commit/224034dcc6263d3b0a8ea20045e435142d8ed2af)
- rename: ThFilterGroup -&gt; ThFilterStatus [`2a6a399`](https://git.odit.services/lfk/frontend/commit/2a6a39916a03c0466e63354e9f5ad7924cb59b6b)
- new license file version [CI SKIP] [`0e5490f`](https://git.odit.services/lfk/frontend/commit/0e5490f1c84217a5a6d5c8745c4667b32ca65e1a)
- new license file version [CI SKIP] [`026d3d4`](https://git.odit.services/lfk/frontend/commit/026d3d41c1b976a4dc7c733576a6a9e8d4b13b78)
- Updated breakpoints [`452d010`](https://git.odit.services/lfk/frontend/commit/452d0101838d72bff7d588a953faae028e2ff819)
- Tailwind bump [`a101873`](https://git.odit.services/lfk/frontend/commit/a101873eb0946b284a11a5081642711f5087da14)
- Fixed checkbox show [`0900c26`](https://git.odit.services/lfk/frontend/commit/0900c2691e4cfe5046e8ae186c8ac8884c90abd6)
- Removed unused console log [`aafc4c8`](https://git.odit.services/lfk/frontend/commit/aafc4c8d62a7a0a493c8bd60149f90c842534bdd)
- i18n import [`6fe134a`](https://git.odit.services/lfk/frontend/commit/6fe134afc8bfef4e7470b7e53b9312b172a7322b)
- Merge pull request 'fix: RunnerDetail: set .phone to null if empty' (#152) from bugfix/151-runnerdetail--cannot-unset-phone-number into dev [`329c1cc`](https://git.odit.services/lfk/frontend/commit/329c1cc037a43c818ba3b6c72581d29586d76232)
- Merge pull request 'feature/146-runner-table-performance-data-table' (#150) from feature/146-runner-table-performance-data-table into dev [`b82d638`](https://git.odit.services/lfk/frontend/commit/b82d638de1aa1f72aada212cf3e4147d808b4fcf)
- Merge pull request 'feature/148-dashboard_statscards' (#149) from feature/148-dashboard_statscards into dev [`fd1a06b`](https://git.odit.services/lfk/frontend/commit/fd1a06b3595b3713ad474e623c74105125602d46)
- Fixed top checkbox state [`3d2acb6`](https://git.odit.services/lfk/frontend/commit/3d2acb692a28c116790248679e238fb562b24ac5)
#### [0.16.1](https://git.odit.services/lfk/frontend/compare/0.16.0...0.16.1)
> 15 February 2023
- fix: donor detail: sponsorings: unset middlename will show as "null" [`#145`](https://git.odit.services/lfk/frontend/issues/145)
- 🚀RELEASE v0.16.1 [`4499480`](https://git.odit.services/lfk/frontend/commit/449948050b8673d43a8dfbb225c3198e4bbb3c7b)
#### [0.16.0](https://git.odit.services/lfk/frontend/compare/0.15.6...0.16.0)
> 3 February 2023
- First page for statsclients [`f299617`](https://git.odit.services/lfk/frontend/commit/f299617c600d2bba7b4405c7c3acae9fd93aefa8)
- 🚀RELEASE v0.16.0 [`75684ef`](https://git.odit.services/lfk/frontend/commit/75684efa1ae0edb4b4d414757c5acf2a77c572e5)
- Basic statsclient detail [`0215860`](https://git.odit.services/lfk/frontend/commit/02158605be824e5ac21a6284731138190988c794)
- Updated Add modal [`f679330`](https://git.odit.services/lfk/frontend/commit/f679330466205e6480cd7f2b7c2b4fdc41c51525)
- Switched drone to kaniko [`1c98005`](https://git.odit.services/lfk/frontend/commit/1c980059cff5c87c452428b53513507c2339451f)
- Re-added copy modal [`fecb07e`](https://git.odit.services/lfk/frontend/commit/fecb07ee373dcaaeaea69fdf8d4c6ee2c257c89c)
- Added Statsclients to sidebar [`068076d`](https://git.odit.services/lfk/frontend/commit/068076dd47373c673a25e730cb8a57c686682810)
- Fixed imports and naming [`f3cc07c`](https://git.odit.services/lfk/frontend/commit/f3cc07c009ed0a34e61f1aad47a1a31778145439)
- new license file version [CI SKIP] [`2c4f27a`](https://git.odit.services/lfk/frontend/commit/2c4f27a943bb35be6728bb49bd5c2263cba78165)
- Merge pull request 'feature/143-beamershow_clients' (#144) from feature/143-beamershow_clients into dev [`53b7dec`](https://git.odit.services/lfk/frontend/commit/53b7dec7cd516c908d45591b855f4be09371f9b1)
- Updated deletion modal [`93fc7c2`](https://git.odit.services/lfk/frontend/commit/93fc7c2e83f78dd88f15d9246127bb9e69f1a8ee)
- Updated mounted variables [`674e6a9`](https://git.odit.services/lfk/frontend/commit/674e6a90ec23dde9377bea64c14a50e41ffa450d)
- Removed Key after creation [`e10c648`](https://git.odit.services/lfk/frontend/commit/e10c6480a504338b21e30fdf2577e5b6c3b635db)
- Updated docker base images [`9767553`](https://git.odit.services/lfk/frontend/commit/976755338b8621064f9a73147aa600af1f77cd51)
- Added translation [`96c55db`](https://git.odit.services/lfk/frontend/commit/96c55db63dbfed92b78ff0e7bdab7a8cce4d76e9)
- Pinned versions [`cff112d`](https://git.odit.services/lfk/frontend/commit/cff112d705a74a135286943298f3f344341325ac)
- Tailwind bump [`e0cbfb0`](https://git.odit.services/lfk/frontend/commit/e0cbfb000bee59a71e06bd58a9c7ef6a0fc7091d)
- Added missing translation [`19a333d`](https://git.odit.services/lfk/frontend/commit/19a333d7bda525fbcb3c68f3cbf85a4f925a9707)
- Bumped apiclient [`c28f1ee`](https://git.odit.services/lfk/frontend/commit/c28f1ee0bc4456595c21858f38e52ed6f16871c5)
- new license file version [CI SKIP] [`3a66f4c`](https://git.odit.services/lfk/frontend/commit/3a66f4c862db9f35c223cc7007b0560fef4e1d63)
- Bumped apiclient [`28cbc5b`](https://git.odit.services/lfk/frontend/commit/28cbc5b98ca09657100e1740b83aa2617243b26b)
- Ignore pnpm lock [`2d8c4c1`](https://git.odit.services/lfk/frontend/commit/2d8c4c1698a1675f618e85e678012f310f87c6ee)
#### [0.15.6](https://git.odit.services/lfk/frontend/compare/0.15.5...0.15.6)
> 19 July 2021
- 🚀RELEASE v0.15.6 [`9fc4ad6`](https://git.odit.services/lfk/frontend/commit/9fc4ad63c4f77b46d645e83c94b51747b91247b8)
- Fixed donations getting reduced to the first one on certificates [`2391668`](https://git.odit.services/lfk/frontend/commit/2391668a25a1e11a1409df572d77ad1635070fbc)
- new license file version [CI SKIP] [`97054a7`](https://git.odit.services/lfk/frontend/commit/97054a71c1ab8a045762a55148124965c6994373)
#### [0.15.5](https://git.odit.services/lfk/frontend/compare/0.15.4...0.15.5)
> 5 July 2021
- 🚀RELEASE v0.15.5 [`717d335`](https://git.odit.services/lfk/frontend/commit/717d33547c3378424dd720005da9952f8a753f1a)
- Merge pull request 'Fixed kilometer conversion' (#142) from bugfix/141-runner_kilometers into dev [`997be32`](https://git.odit.services/lfk/frontend/commit/997be32679dc38c9fb0e92b6ce011057b854d99d)
- Fixed kilometer conversion [`134f00c`](https://git.odit.services/lfk/frontend/commit/134f00c40e0c8252e7604a73151e8d6685b2c61d)
- new license file version [CI SKIP] [`e752ee1`](https://git.odit.services/lfk/frontend/commit/e752ee12d17a4423f4364f7766eafbe7d4cef2d1)
#### [0.15.4](https://git.odit.services/lfk/frontend/compare/0.15.3...0.15.4)
> 5 July 2021
- Merge pull request 'fix total donation sum in dashboard' (#140) from bugfix/139-total-donation-sum-is-wrong into dev [`#139`](https://git.odit.services/lfk/frontend/issues/139)
- 🚀RELEASE v0.15.4 [`cc4515f`](https://git.odit.services/lfk/frontend/commit/cc4515ff66b1c1de3747d0ee6cc465574accedb7)
- divide by 100 + toFixes(2) [`b246f2b`](https://git.odit.services/lfk/frontend/commit/b246f2b349b06d1adea318dfad58f97fb1a249bb)
#### [0.15.3](https://git.odit.services/lfk/frontend/compare/0.15.2...0.15.3)
> 16 April 2021
- 🚀RELEASE v0.15.3 [`76b69d8`](https://git.odit.services/lfk/frontend/commit/76b69d851aa590ecf8caac135b72962a72e83635)
- 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)
> 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)
#### [0.8.6](https://git.odit.services/lfk/frontend/compare/0.8.5...0.8.6)
> 25 March 2021
- 🚀RELEASE v0.8.6 [`c3c95bf`](https://git.odit.services/lfk/frontend/commit/c3c95bf2916618efe6764a33d9a42d35764d15be)
- Merge pull request 'Know Production Bugs 🐞' (#109) from bugfix/107-prod_issues into dev [`d2050b5`](https://git.odit.services/lfk/frontend/commit/d2050b5948890a6077cbb41d82d1a6a1d1106652)
- Errors now toast errors❌ [`17e0805`](https://git.odit.services/lfk/frontend/commit/17e0805fe64f6d181f55b81afa502ee6443ebabe)
- Sorted translations 👀 [`82b1811`](https://git.odit.services/lfk/frontend/commit/82b1811971b974b686e7618b8a381e1589c168f6)
- Fixed missing translations for scanstations🌍 [`aeadef6`](https://git.odit.services/lfk/frontend/commit/aeadef60bbe71da09bb569d20ca7377645beba7f)
- Sorted translations🌍 [`a1ab65a`](https://git.odit.services/lfk/frontend/commit/a1ab65a0e975c02c01c603bf6d95a79ada1caa0b)
- Fixed runner import getting triggered with invalid information [`ddd9c39`](https://git.odit.services/lfk/frontend/commit/ddd9c396b6bfd39a7b1627d4975151943b367ebf)
- Removed middlename search from all files that had it [`6b92405`](https://git.odit.services/lfk/frontend/commit/6b92405bae21e78d694601cbc0b33eed56ef4533)
- Fixed mail login bug🐞📧 [`0768939`](https://git.odit.services/lfk/frontend/commit/076893981ff4f7f17330746c561acc570339adac)
- Now disabled search by middlename as a quick workaround 🐞 [`49e87cc`](https://git.odit.services/lfk/frontend/commit/49e87ccb15a7ed5edea22a3c3e235f7bee07d3f4)
- Fixed conflicting css [`50fffef`](https://git.odit.services/lfk/frontend/commit/50fffef13b8fce885964d8ac277b4ca24d944b2b)
- Commented out the buggy runner search to prevent bad UX [`fbe74a5`](https://git.odit.services/lfk/frontend/commit/fbe74a5d8090553a35576a17c97019939cf4f386)
- Fixed outsideclick not clearing import modal🛠 [`ef49e50`](https://git.odit.services/lfk/frontend/commit/ef49e507c175510eeb466d33f222755fac8a2a0b)
#### [0.8.5](https://git.odit.services/lfk/frontend/compare/0.8.4...0.8.5)
> 20 March 2021
- 🚀RELEASE v0.8.5 [`e838e6f`](https://git.odit.services/lfk/frontend/commit/e838e6f321bef1565a7e4316890a3c600b242e5a)
- Fixed dupliacate mutation 🐞 [`91dd525`](https://git.odit.services/lfk/frontend/commit/91dd5256e9545f62e4342ae5477c36262d6e3401)
#### [0.8.4](https://git.odit.services/lfk/frontend/compare/0.8.3...0.8.4)
> 20 March 2021
- CONFIG: default_username + default_password [`cc926e8`](https://git.odit.services/lfk/frontend/commit/cc926e84fb8bd9d6c9fd37349e25eb802e1bb324)
- 🚀RELEASE v0.8.4 [`3d4dc2d`](https://git.odit.services/lfk/frontend/commit/3d4dc2d72b129f0134ae9f230810c3301dbd5caa)
- CONFIG: add 'demo' as default username/password [`ba34710`](https://git.odit.services/lfk/frontend/commit/ba3471068ab00e2d5dbe21d6d763094e662f8347)
#### [0.8.3](https://git.odit.services/lfk/frontend/compare/0.8.2...0.8.3)
> 20 March 2021
- Sorted translation 🌍 [`d6f6d10`](https://git.odit.services/lfk/frontend/commit/d6f6d10cb6b639a1f988e0da4811355750b0f027)
- Smaller bugfixes [`8e04377`](https://git.odit.services/lfk/frontend/commit/8e0437728bd04223a23cdf1879c6c739ca8ebef7)
- More small fixes [`1249904`](https://git.odit.services/lfk/frontend/commit/12499045824c13a3ee35c6cc8c3c3a3130dbec12)
- 🚀RELEASE v0.8.3 [`fff16e6`](https://git.odit.services/lfk/frontend/commit/fff16e6650cce1231a8d0db43531bf8e3e01f84a)
#### [0.8.2](https://git.odit.services/lfk/frontend/compare/0.8.1...0.8.2)
> 20 March 2021
- Now using env base url [`aa8196d`](https://git.odit.services/lfk/frontend/commit/aa8196db3a9ff1bcd2b5f0ce655957bfa4e310ab)
- 🚀RELEASE v0.8.2 [`fb0c071`](https://git.odit.services/lfk/frontend/commit/fb0c0718e405312f239bc3829b3d9c203d246458)
#### [0.8.1](https://git.odit.services/lfk/frontend/compare/0.8.0...0.8.1)
> 20 March 2021
- ⚡ CI - add build:tags pipeline [`910a086`](https://git.odit.services/lfk/frontend/commit/910a0860a0bd240c5ff9d23dc33923f941b1e10b)
- 🚀RELEASE v0.8.1 [`b2223b5`](https://git.odit.services/lfk/frontend/commit/b2223b5110cc31740e69de66c74ab3f83a046308)
#### [0.8.0](https://git.odit.services/lfk/frontend/compare/0.7.0...0.8.0)
> 20 March 2021
- 🚀RELEASE v0.8.0 [`009431f`](https://git.odit.services/lfk/frontend/commit/009431fb98bc84c71590644a5d3606805d432202)
- new license file version [CI SKIP] [`579ece6`](https://git.odit.services/lfk/frontend/commit/579ece625651c8bf37b5704e82264a8833811f68)
- Merge pull request 'User settings feature/103-settings_page' (#104) from feature/103-settings_page into dev [`822e97d`](https://git.odit.services/lfk/frontend/commit/822e97d3c35be57ecf9ed6bd6af474fd89e7d8d8)
- Sorted translations🌍 [`120d3c9`](https://git.odit.services/lfk/frontend/commit/120d3c9dc81a0878d0dd64b66caadc6201651bda)
@@ -793,7 +1537,7 @@ All notable changes to this project will be documented in this file. Dates are d
- init [`32357ec`](https://git.odit.services/lfk/frontend/commit/32357ece0a7195ea1135c9c3e4c6c84323f95b4d)
- tmp [`1b7173c`](https://git.odit.services/lfk/frontend/commit/1b7173cda9134ee8058a00bdc030defa80d46bfc)
- Login - move to env.js import [`8ef0b21`](https://git.odit.services/lfk/frontend/commit/8ef0b21819309752c573d0485f6514152fb684e6)
- initial commit [`4bb3bae`](https://git.odit.services/lfk/frontend/commit/4bb3bae4e6fc89c35a8a2b36b7cd6e6d47958eae)
- initial commit [`4bb3bae`](https://git.odit.services/lfk/frontend/commit/4bb3bae4e6fc89c35a8a2b36b7cd6e6d47958eae)
- Initial license export [`4c96b9a`](https://git.odit.services/lfk/frontend/commit/4c96b9a3e04dbb7c021c71aa8828a29248509fbe)
- 🚚 move to tinro svelte router [`a50ea15`](https://git.odit.services/lfk/frontend/commit/a50ea15b38023b867a9f7757e973184cbcdd2457)
- new Dashboard [`7270ce9`](https://git.odit.services/lfk/frontend/commit/7270ce9d32869abd4f6ac65ab7c2c87363633cbe)

View File

@@ -1,18 +1,15 @@
FROM node:15.5.1-alpine3.12
FROM registry.odit.services/hub/library/node:19.7.0-alpine3.16 as build
ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app
RUN npm i -g pnpm
COPY package.json ./
RUN pnpm i
COPY package.json *.config.js workbox-config.js template-copy.js index.template.html s-config.template.js ./
COPY package.json pnpm-lock.yaml *.config.js *.config.cjs index.html ./
RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8 && pnpm i
COPY src ./src
COPY public ./public
RUN pnpm run build
RUN pnpm build
# final image
FROM alpine
COPY --from=0 /app/build /app
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
FROM registry.odit.services/library/nginx-brotli:3.15 as final
COPY --from=build /app/dist /usr/share/nginx/html
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

@@ -1,7 +0,0 @@
const config = {
baseurl: 'https://dev.lauf-fuer-kaya.de',
documentserver_key: 'NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe',
fallback_username: 'admin',
fallback_password: '72fpTzsev4xUu78QPs2FCbwZ3',
prefersHashRouting: true
};

View File

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

View File

@@ -6,6 +6,20 @@ http {
server {
error_page 404 /index.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 / {
try_files $uri $uri/ /index.html;
}

View File

@@ -1,4 +1,4 @@
const fs = require('fs');
import fs from 'fs'
// get all language files
const files = fs.readdirSync('./src/locales/');
files.forEach((f) => {

View File

@@ -1,61 +1,61 @@
{
"name": "@odit/lfk-frontend",
"version": "0.8.0",
"scripts": {
"i18n-order": "node order.js",
"dev:all": "yarn prebuild && snowpack dev",
"dev": "cross-env NODE_ENV_ODIT=development_fast node template-copy.js && yarn build:sw && snowpack dev",
"build": "yarn prebuild && snowpack build",
"prebuild": "cross-env NODE_ENV_ODIT=production node template-copy.js && yarn build:sw",
"build:sw": "workbox generateSW workbox-config.js",
"release": "release-it",
"licenses:export": "license-exporter --json -o public"
},
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@odit/lfk-client-js": "0.6.4",
"csvtojson": "^2.0.10",
"gridjs": "3.3.0",
"localforage": "1.9.0",
"marked": "^2.0.1",
"svelte-focus-trap": "1.0.1",
"svelte-i18n": "3.3.7",
"svelte-select": "^3.17.0",
"tailwindcss": "2.0.3",
"tinro": "0.6.1",
"toastify-js": "1.9.3",
"validator": "13.5.2",
"xlsx": "^0.16.9"
},
"devDependencies": {
"@odit/license-exporter": "^0.0.11",
"@snowpack/plugin-svelte": "3.5.2",
"auto-changelog": "^2.2.1",
"autoprefixer": "10.2.5",
"cross-env": "^7.0.3",
"postcss": "8.2.8",
"postcss-load-config": "3.0.1",
"release-it": "^14.4.1",
"snowpack": "3.0.13",
"svelte": "3.35.0",
"svelte-preprocess": "4.6.9",
"workbox-cli": "6.1.2"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀RELEASE v${version}",
"push": false,
"tag": true,
"tagName": null,
"tagAnnotation": "v${version}"
},
"npm": {
"publish": false
},
"hooks": {
"after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js && git add index.template.html && node order.js && git add src/locales"
}
}
}
{
"name": "@odit/lfk-frontend",
"version": "1.3.0",
"type": "module",
"scripts": {
"i18n-order": "node order.js",
"dev": "vite",
"build": "vite build",
"release": "release-it",
"licenses:export": "license-exporter --json -o public"
},
"license": "CC-BY-NC-SA-4.0",
"devDependencies": {
"@odit/license-exporter": "0.0.12",
"@sveltejs/vite-plugin-svelte": "2.0.4",
"auto-changelog": "2.4.0",
"autoprefixer": "10.4.14",
"postcss": "8.4.21",
"release-it": "15.10.1",
"svelte-select": "3.17.0",
"tailwindcss": "3.3.1",
"vite": "4.2.1"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀RELEASE v${version}",
"push": true,
"tag": true,
"tagName": null,
"tagAnnotation": "v${version}"
},
"npm": {
"publish": false
},
"hooks": {
"after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js && git add index.html && node order.js && git add src/locales"
}
},
"dependencies": {
"@odit/lfk-client-js": "1.1.0",
"@paralleldrive/cuid2": "^2.2.0",
"@tanstack/svelte-table": "^8.8.5",
"bwip-js": "^3.4.0",
"check-password-strength": "2.0.7",
"csvtojson": "2.0.10",
"gridjs": "3.4.0",
"localforage": "1.10.0",
"marked": "2.0.3",
"svelte": "3.58.0",
"svelte-i18n": "3.6.0",
"tinro": "0.6.12",
"toastify-js": "1.12.0",
"validator": "13.9.0",
"xlsx": "0.18.5"
},
"volta": {
"node": "19.7.0"
}
}

3950
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

6
postcss.config.cjs Normal file
View File

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

View File

@@ -1,6 +1,9 @@
const config = {
baseurl: 'http://localhost:4010',
documentserver_key: 'NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe'
baseurl_documentserver: 'http://localhost:4010/documents',
documentserver_key: 'NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe',
// optional
default_username: 'demo',
default_password: 'demo',
prefersHashRouting: true
};

View File

@@ -1 +0,0 @@
Nostrud tempor dolor aute ea excepteur aute mollit elit eiusmod exercitation. Magna laborum pariatur adipisicing pariatur cupidatat exercitation duis aliquip pariatur sint exercitation deserunt labore. Consectetur id laboris dolore nostrud do velit ipsum. Eu laboris velit do commodo ad ea sint ex cillum. Cillum ipsum qui eiusmod laborum mollit sunt dolore incididunt. Cillum sunt culpa veniam voluptate et qui ut magna anim occaecat ut mollit dolor. Duis irure proident eu incididunt dolore sunt nisi aute dolore amet eu fugiat laboris quis.

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>
import "./TailwindStyles.svelte";
import "toastify-js/src/toastify.css";
import "gridjs/dist/theme/mermaid.css";
import { Route, router } from "tinro";
@@ -53,7 +52,6 @@
import { OpenAPI } from "@odit/lfk-client-js";
import UserDetail from "./components/users/UserDetail.svelte";
OpenAPI.BASE = config.baseurl;
import { register as registerSW } from "./swmodule";
import TeamDetail from "./components/teams/TeamDetail.svelte";
import UserPermissions from "./components/users/UserPermissions.svelte";
import GroupPermissions from "./components/groups/GroupPermissions.svelte";
@@ -69,13 +67,14 @@
import Donations from "./components/donations/Donations.svelte";
import DonationDetail from "./components/donations/DonationDetail.svelte";
import GroupDetail from "./components/groups/GroupDetail.svelte";
import ScanStationsOverview from "./components/scanstations/ScanStationsOverview.svelte";
import ScanStations from "./components/scanstations/ScanStations.svelte";
import ScanStationDetail from "./components/scanstations/ScanStationDetail.svelte";
import Scans from "./components/scans/Scans.svelte";
import ScanDetail from "./components/scans/ScanDetail.svelte";
import ScanDetail from "./components/scans/ScanDetail.svelte";
import Cards from "./components/cards/Cards.svelte";
import StatsClients from "./components/statsclients/StatsClients.svelte";
import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte";
store.init();
registerSW();
</script>
<Route>
@@ -185,6 +184,14 @@ import ScanDetail from "./components/scans/ScanDetail.svelte";
<DonationDetail {params} />
</Route>
</Route>
<Route path="/cards/*">
<Route path="/">
<Cards />
</Route>
<!-- <Route path="/:scanid" let:params>
<ScanDetail {params} />
</Route> -->
</Route>
<Route path="/scans/*">
<Route path="/">
<Scans />
@@ -201,6 +208,14 @@ import ScanDetail from "./components/scans/ScanDetail.svelte";
<ScanStationDetail {params} />
</Route>
</Route>
<Route path="/statsclients/*">
<Route path="/">
<StatsClients />
</Route>
<Route path="/:clientid" let:params>
<StatsClientDetail {params} />
</Route>
</Route>
<Route path="/about">
<About />
</Route>

View File

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

View File

@@ -9,7 +9,7 @@
let usersEmail = "";
function reset() {
if (isEmail(usersEmail)) {
AuthService.authControllerGetResetToken({ email: usersEmail })
AuthService.authControllerGetResetToken("de", { email: usersEmail })
.then((resp) => {
Toastify({
text: $_("mail-validation-in-progress"),

View File

@@ -5,10 +5,11 @@
store.init();
import { OpenAPI, AuthService } from "@odit/lfk-client-js";
import Footer from "../general/Footer.svelte";
import isEmail from "validator/es/lib/isEmail";
import Toastify from "toastify-js";
// ------
let username = "demo";
let password = "demo";
let username = config.default_username || "";
let password = config.default_password || "";
let is_blocked_by_autologin = false;
let last_loginclick_processed = true;
@@ -36,10 +37,19 @@
text: $_("login_is_checked"),
duration: 500,
}).showToast();
AuthService.authControllerLogin({
username,
password,
})
let postdata = {};
if (isEmail(username)) {
postdata = {
email: username,
password,
};
} else {
postdata = {
username,
password,
};
}
AuthService.authControllerLogin(postdata)
.then(async (result) => {
await localForage.setItem("logindata", result);
OpenAPI.TOKEN = result.access_token;
@@ -66,7 +76,7 @@
// last login was not processed yet
} else {
Toastify({
text: "chill...",
text: $_('please-wait-a-moment-your-login-is-still-being-processed'),
duration: 1500,
backgroundColor:
"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>
import { AuthService } from "@odit/lfk-client-js";
import { AuthService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import "toastify-js/src/toastify.css";
import PasswordStrength, {
password_strong_enough,
} from "../auth/PasswordStrength.svelte";
let state = "reset_in_progress";
let password = "";
export let params;
function set_new_password() {
if(password.trim() !== ""){
Toastify({
text: $_('password-reset-in-progress'),
duration: 3500,
}).showToast();
AuthService.authControllerResetPassword(atob(params.resetkey),{ password })
if (password.trim() !== "") {
Toastify({
text: $_("password-reset-in-progress"),
duration: 3500,
}).showToast();
AuthService.authControllerResetPassword(atob(params.resetkey), {
password,
})
.then((resp) => {
Toastify({
text: $_('password-reset-successful'),
text: $_("password-reset-successful"),
duration: 3500,
}).showToast();
state="reset_success";
state = "reset_success";
})
.catch((err) => {
state="reset_error";
state = "reset_error";
});
} else {
} else {
Toastify({
text: $_('please-provide-a-password'),
text: $_("please-provide-a-password"),
duration: 3500,
}).showToast();
}
}
</script>
{#if state==="reset_success"}
{#if state === 'reset_success'}
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
@@ -56,31 +61,31 @@
</div>
</div>
</div>
{:else if state==="reset_error"}
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6">
<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">
{$_('application_name')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold">
{$_('password-reset-failed')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_('please-request-a-new-reset-mail')}
</p>
<div class="mt-6">
{:else if state === 'reset_error'}
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6">
<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">
{$_('application_name')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold">
{$_('password-reset-failed')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_('please-request-a-new-reset-mail')}
</p>
<div class="mt-6">
<a
href="/forgot_password/"
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">
{$_('request-a-new-reset-mail')}
</a>
<div class="mt-6">
<a
href="/forgot_password/"
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">
{$_('request-a-new-reset-mail')}
</a>
</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="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
@@ -102,11 +107,14 @@
placeholder={$_('new-password')}
bind:value={password} />
</div>
<PasswordStrength bind:password_change={password} />
</div>
<div class="mt-5">
<button
on:click={set_new_password}
disabled={!password_strong_enough(password)}
class:opacity-50={!password_strong_enough(password)}
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">
<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

@@ -0,0 +1,243 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { RunnerCardService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let bulk_modal_open;
function focus(el) {
el.focus();
}
const dispatch = createEventDispatcher();
$: card_count = 0;
$: is_card_count_valid = card_count > 0;
$: processed_last_submit = true;
$: createbtnenabled = is_card_count_valid;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
bulk_modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit_with_print();
}
}
};
})();
function submit_without_print() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("creating-blanco-cards"),
duration: -1,
}).showToast();
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, true)
.then((result) => {
bulk_modal_open = false;
//
Toastify({
text: $_("created-blanco-cards"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
dispatch("created", {cards: result})
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
function submit_with_print() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("creating-blanco-cards"),
duration: -1,
}).showToast();
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, true)
.then((result) => {
bulk_modal_open = false;
//
Toastify({
text: $_("created-blanco-cards"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
dispatch("created", {cards: result})
fetch(
`${config.baseurl_documentserver}/cards?&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(result),
}
)
.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 = "Bulkcards.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);
});
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if bulk_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
bulk_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-2xl 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- 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

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

View File

@@ -0,0 +1,193 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { RunnerCardService, RunnerService } from "@odit/lfk-client-js";
import Select from "svelte-select";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let edit_modal_open;
export let runner = {};
export let editable = {};
export let original_data = {};
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) => {
if (filterText.startsWith("#")) {
return option.value.id == parseInt(filterText.replace("#",""))
}
return (
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase())
);
};
function focus(el) {
el.focus();
}
$: runners = [];
$: enabled = true;
$: processed_last_submit = true;
const dispatch = createEventDispatcher();
RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r };
});
});
$: createbtnenabled = !(
JSON.stringify(editable) === JSON.stringify(original_data)
);
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
edit_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-card"),
duration: -1,
}).showToast();
RunnerCardService.runnerCardControllerPut(original_data.id, editable)
.then((result) => {
let id = original_data.id;
runner = {};
editable = {};
original_data = {};
edit_modal_open = false;
//
Toastify({
text: $_("card-updated"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
dispatch('dataUpdated', {card: result});
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if edit_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
edit_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">
{$_('edit-a-card')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('you-can-provide-a-runner-but-you-dont-have-to')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="runner"
class="block text-sm font-medium text-gray-700">{$_('runner')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterRunners(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_('search-for-runner-by-name-or-id')}
noOptionsMessage={$_('no-runners-found')}
bind:selectedValue={runner}
on:select={(selectedValue) => (editable.runner = selectedValue.detail.value.id)}
on:clear={() => (editable.runner = null)} />
</div>
<div class="col-span-6">
<p class="text-gray-500">
<input
id="enabled"
on:change={() => {
editable.enabled = !editable.enabled;
}}
name="enabled"
type="checkbox"
checked={editable.enabled}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
{$_('this-card-is')}
{#if editable.enabled}
{$_('enabled')}
{:else}{$_('disabled')}{/if}
</p>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('save-changes')}
</button>
<button
on:click={() => {
edit_modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,16 @@
<script>
import { _ } from "svelte-i18n";
export let runner;
</script>
{#if !runner}
{$_("non-blanko")}
{:else}
<a href={`/runners/${runner.id}`}>
{#if runner.middlename}
{runner.firstname} {runner.middlename} {runner.lastname}
{:else}
{runner.firstname} {runner.lastname}
{/if}
</a>
{/if}

View File

@@ -0,0 +1,16 @@
<script>
import { _ } from "svelte-i18n";
export let enabled = false;
</script>
{#if 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}

View File

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

View File

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

View File

@@ -0,0 +1,315 @@
<script>
import { _ } from "svelte-i18n";
import { RunnerCardService } from "@odit/lfk-client-js";
import store from "../../store";
import Toastify from "toastify-js";
import CardsEmptyState from "./CardsEmptyState.svelte";
import CardDetailModal from "./CardDetailModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import InputElement from "../shared/InputElement.svelte";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { writable } from "svelte/store";
import TableBottom from "../shared/TableBottom.svelte";
import TableActions from "../shared/TableActions.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import CardStatus from "./CardStatus.svelte";
import CardRunner from "./CardRunner.svelte";
import { onMount } from "svelte";
import { runnerFilter, statusFilter } from "../shared/tablefilters";
import DeleteCardModal from "./DeleteCardModal.svelte";
export let edit_modal_open = false;
export let runner = {};
export let editable = {};
export let original_data = {};
export let current_cards = [];
export const addCards = (cards) => {
console.log(cards);
current_cards = current_cards.concat(...cards);
options.update((options) => ({
...options,
data: current_cards,
}));
};
$: dataLoaded = false;
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: selectedCards =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: active_delete = undefined;
$: cards_show = generate_cards.length > 0;
$: generate_cards = [];
const columns = [
{
accessorKey: "code",
header: () => $_("code"),
filterFn: `includesString`,
},
{
accessorKey: "runner",
header: () => $_("runner"),
cell: (info) => {
return renderComponent(CardRunner, { runner: info.getValue() });
},
filterFn: `runner`,
},
{
accessorKey: "enabled",
cell: (info) => {
return renderComponent(CardStatus, { enabled: info.getValue() });
},
header: () => $_("status"),
filterFn: `status`,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsAction: () => {
open_edit_modal(
current_cards[
current_cards.findIndex((r) => r.id == info.row.original.id)
]
);
},
deleteAction: () => {
active_delete =
current_cards[
current_cards.findIndex((r) => r.id == info.row.original.id)
];
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes("CARD:DELETE"),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
filterFns: {
runner: runnerFilter,
status: statusFilter,
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
function open_edit_modal(card) {
const getRunnerLabel = (option) =>
option.firstname +
" " +
(option.middlename || "") +
" " +
option.lastname;
if (card.runner?.id) {
runner = Object.assign(
{ runner },
{ label: getRunnerLabel(card.runner), value: card.runner }
);
card.runner = card.runner.id;
} else {
card.runner = null;
runner = null;
}
editable = Object.assign(editable, card);
original_data = Object.assign(original_data, card);
edit_modal_open = true;
}
async function deleteCard(delete_card_id) {
await RunnerCardService.runnerCardControllerRemove(delete_card_id, true);
current_cards = current_cards.filter((r) => r.id !== delete_card_id);
options.update((options) => ({
...options,
data: current_cards,
}));
Toastify({
text: $_("card-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
onMount(async () => {
let page = 0;
while (page >= 0) {
const cards = await RunnerCardService.runnerCardControllerGetAll(
page,
500
);
if (cards.length == 0) {
page = -2;
}
current_cards = current_cards.concat(...cards);
options.update((options) => ({
...options,
data: current_cards,
}));
dataLoaded = true;
page++;
}
console.log("All cards loaded");
});
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:UPDATE")}
<CardDetailModal
bind:edit_modal_open
bind:runner
bind:editable
bind:original_data
on:dataUpdated={(event) => {
current_cards[
current_cards.findIndex((c) => c.id === event.detail.card.id)
] = event.detail.card;
current_cards = current_cards;
options.update((options) => ({
...options,
data: current_cards,
}));
}}
/>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")}
<DeleteCardModal
delete_card={active_delete}
modal_open={active_delete != undefined}
on:delete={(event) => {
deleteCard(event.detail.id);
}}
/>
{#if !dataLoaded}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("loading-cards")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:else if current_cards.length === 0}
<CardsEmptyState />
{:else}
<div class="h-12 mt-1">
{#if selected.length > 0}
<button
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 inline-flex"
id="options-menu"
on:click={async () => {
const prom = [];
for (const card of selectedCards) {
prom.push(
await RunnerCardService.runnerCardControllerRemove(
card.id,
true
)
);
}
await Promise.all(prom);
for (const card of selectedCards) {
current_cards = current_cards.filter((r) => r.id !== card.id);
}
options.update((options) => ({
...options,
data: current_cards,
}));
$table.resetRowSelection();
}}
>
{$_("delete-cards")}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
{/if}
<GenerateRunnerCards
cards_show={selected.length > 0}
bind:generate_cards={selectedCards}
/>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
{#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<InputElement
type="checkbox"
checked={$table.getIsAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()}
/>
</th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr>
{/each}
</thead>
<tbody>
{#each $table.getRowModel().rows as row}
<tr>
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement
type="checkbox"
checked={row.getIsSelected()}
on:change={() => row.toggleSelected()}
/>
</td>
{#each row.getVisibleCells() as cell}
<td>
<svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
<TableBottom {table} {selected} />
{/if}
{/if}

View File

@@ -0,0 +1,128 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_card = {
id: 0,
code: "",
runner: {
firstname: "",
lastname: "",
},
};
const dispatch = createEventDispatcher();
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
async function submit() {
dispatch("delete", { id: delete_card.id });
modal_open = false;
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="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">
{$_("confirm-delete")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_("please-confirm-the-deletion-of-card")}
</p>
</div>
<div class="w-full">
{$_("card")} #{delete_card.code}<br />
<span class="inline-block">
{$_("runner")}:
{#if delete_card.runner}
<span class="inline-block"
>{delete_card.runner.firstname}
{delete_card.runner.lastname}</span
>
{:else}
{$_("non-blanko")}
{/if}</span
>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={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"
>
{$_("delete")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,57 @@
<script>
export let handler;
let filterValue = "";
</script>
<th>
<input
on:input={() => {
setTimeout(() => {
const v = filterValue.toLowerCase();
handler.filter(v, (c) => {
// if (v === "") {
// return c;
// }
if (!c.runner && v === "blanko") {
return "blanko";
}
if (v.startsWith("#")) {
return `#${c.runner?.id}`;
}
if (c.runner) {
let runnerName = `${c.runner.firstname} ${c.runner.lastname}`;
if (c.runner.middlename) {
runnerName = `${c.runner.firstname} ${c.runner.middlename} ${c.runner.lastname}`;
}
runnerName = runnerName.toLowerCase();
return runnerName;
}
return "";
});
}, 150);
}}
bind:value={filterValue}
type="text"
name="runnerfilter"
id="runnerfilter"
/>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
input {
margin: -1px 0 0 0;
padding: 0;
width: 100%;
height: 24px;
border: none;
text-align: left;
background: inherit;
outline: 0;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,45 @@
<script>
import { _ } from "svelte-i18n";
export let handler;
let selected = "all";
</script>
<th>
<select
on:input={() => {
setTimeout(() => {
if (`${selected}`.trim()) {
if (selected === "all") {
handler.filter("", "enabled");
} else {
handler.filter(selected, "enabled");
}
}
}, 50);
}}
bind:value={selected}
name="statusfilter"
id="statusfilter"
>
<option value="all">{$_("all")}</option>
<option value="true">{$_("enabled")}</option>
<option value="false">{$_("disabled")}</option>
</select>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
select {
margin: -1px 0 0 0;
padding: 0;
width: 100%;
height: 24px;
border: none;
text-align: left;
background: inherit;
outline: 0;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1 @@
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 653.9 247.6"><path d="M272 211l-53 12s-11-2-1-17l4-4 27-14v-2l-6-41-2-16 17-17 4-3 44 41v1l-19 33z" fill="#ffb7b7"/><path d="M253 198l-54 13a6 6 0 01-5-7 16 16 0 012-5 48 48 0 016-9l28-14-4-24-1-12-2-8-2-16 21-19 22 21 22 20-3 5-3 7-17 30-3 6z" fill="#ffb7b7"/><path d="M346 190s-20-1-28-15a24 24 0 01-3-14l-8-17-11-23-30-4-2 1-21 19-7 6-10 9-49 44a37 37 0 01-7 9 50 50 0 01-9 7c-10 5-24 9-44 7L10 248 0 176l89-29 131-86 89 23 41 58z" fill="#ffb7b7"/><path d="M648 0H275a5 5 0 00-5 5v221a5 5 0 005 6h373a5 5 0 006-6V5a5 5 0 00-6-5z" fill="#fff"/><path d="M648 0H275a5 5 0 00-5 5v221a5 5 0 005 6h373a5 5 0 006-6V5a5 5 0 00-6-5zm4 226a4 4 0 01-4 4H275a4 4 0 01-3-4V5a4 4 0 013-3h373a4 4 0 014 3z" fill="#3f3d56"/><path d="M312 30a9 9 0 119-9 9 9 0 01-9 9zm0-17a8 8 0 107 8 8 8 0 00-7-8z" fill="#6c63ff"/><path d="M297 21a8 8 0 016-8 8 8 0 100 16 8 8 0 01-6-8zM349 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM368 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM386 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM415 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM434 130a7 7 0 01-7-7v-20a7 7 0 0113 0v20a7 7 0 01-6 7zM452 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM481 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM499 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM518 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM546 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM565 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM583 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7z" fill="#6c63ff"/><path d="M396 208h-99a5 5 0 110-10h99a5 5 0 010 10zM364 188h-35a5 5 0 110-10h35a5 5 0 110 10z" fill="#e6e6e6"/><path fill="#3f3d56" d="M271 46h381v2H271z"/><path opacity=".1" d="M228 203l-1-2 33-15 8-27-12-10 1-1 13 10-8 30-34 15zM196 199l-9 4-17 2a50 50 0 01-9 7l-16-1-26-1 88-74 18 4-29 59z"/><path d="M318 175l-8 1-47 4-29 1-38 18-9 4-70 8 95-81 11 2 20 5 22 5 18 1 24 1 20 1a13 13 0 0112 13c0 7-5 14-21 17z" fill="#ffb7b7"/><path d="M325 170s-7-2-9-9c-2-4-1-9 3-15l1 1c-3 6-4 10-3 14 2 5 9 7 9 7zM197 197l34-16v2l-33 16zM218 135l48-19v2l-41 16 35 6v2l-42-7z" opacity=".1"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
GroupContactService,
RunnerTeamService,
@@ -86,7 +86,7 @@
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: "Contact is being added...",
text: $_('contact-is-being-added'),
duration: -1,
}).showToast();
let address = {};
@@ -123,7 +123,7 @@
modal_open = false;
//
Toastify({
text: "Contact added",
text: $_('contact-added'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
@@ -145,7 +145,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -31,7 +31,7 @@
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
class="mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">

View File

@@ -1,308 +1,361 @@
<script>
import { _ } from "svelte-i18n";
import localForage from "localforage";
import store from "../../store";
import { router } from "tinro";
import NoComponentLoaded from "../base/NoComponentLoaded.svelte";
import { AuthService } from "@odit/lfk-client-js";
$: navOpen = false;
function logout() {
localForage.clear();
location.replace("/");
}
</script>
<section class="min-h-screen bg-gray-50">
<nav
class:-translate-x-full={!navOpen}
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">
<a href="/" class="flex items-center px-4 py-5">
<img src="/lfk-logo.png" alt="Logo" class="h-10" />
<h3 class="text-lg">Lauf für Kaya! Admin</h3>
</a>
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation">
<a
class:bg-gray-100={$router.path === '/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
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" />
</svg>
<span>{$_('dashboard-title')}</span>
</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
<a
class:bg-gray-100={$router.path.includes('/orgs/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/orgs/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="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>
<span>{$_('orgs')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')}
<a
class:bg-gray-100={$router.path === '/users/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/users/">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 14v8H4a8 8 0 018-8zm0-1a6 6 0 110-12 6 6 0 010 12zm2.6 5.81a3.51 3.51 0 010-1.62l-1-.57 1-1.74 1 .58a3.5 3.5 0 011.4-.82V13.5h2v1.15a3.5 3.5 0 011.4.8l1-.57 1 1.74-1 .57a3.51 3.51 0 010 1.62l1 .57-1 1.74-1-.58a3.5 3.5 0 01-1.4.82v1.14h-2v-1.15a3.5 3.5 0 01-1.4-.8l-1 .57-1-1.74 1-.57zM18 17a1 1 0 100 2 1 1 0 000-2z" /></svg>
<span>{$_('users')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')}
<a
class:bg-gray-100={$router.path === '/groups/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/groups/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('user-groups')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
<a
class:bg-gray-100={$router.path === '/runners/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/runners/">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
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>
<span>{$_('runners')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
<a
class:bg-gray-100={$router.path === '/teams/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/teams/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
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>
<span>{$_('teams')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
<a
class:bg-gray-100={$router.path.includes('/donors/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donors/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg>
<span>{$_('donors')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
<a
class:bg-gray-100={$router.path.includes('/donations/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donations/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg>
<span>{$_('donations')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')}
<a
class:bg-gray-100={$router.path === '/tracks/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/tracks/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
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>
<span>{$_('tracks')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')}
<a
class:bg-gray-100={$router.path === '/scans/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/scans/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg>
<span>Scans</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')}
<a
class:bg-gray-100={$router.path === '/contacts/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/contacts/">
<svg
fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg>
<span>{$_('contacts')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')}
<a
class:bg-gray-100={$router.path === '/scanstations/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/scanstations/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
<span>{$_('scanstations')}</span>
</a>
{/if}
<a
class:bg-gray-100={$router.path === '/settings/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/settings/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
fill-rule="evenodd"
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"
clip-rule="evenodd" />
</svg>
<span>{$_('settings')}</span>
</a>
<a
class:bg-gray-100={$router.path === '/about/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/about/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
<path d="M12 16v-4M12 8h.01" /></svg>
<span>{$_('about')}</span>
</a>
<span
tabindex="0"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
on:click={() => {
AuthService.authControllerLogout();
logout();
}}>
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
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>
<span>{$_('logout')}</span>
</span>
</nav>
</nav>
<div class="ml-0 transition md:ml-60">
<header
on:click={() => {
navOpen = true;
}}
class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden">
<button class="block btn btn-light md:hidden">
<span class="sr-only">Menu</span><svg
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentcolor"><path
fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clip-rule="evenodd" /></svg></button>
</header>
<slot>
<NoComponentLoaded />
</slot>
</div>
<div
on:click={() => {
navOpen = false;
}}
class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" />
</section>
<script>
import { _ } from "svelte-i18n";
import localForage from "localforage";
import store from "../../store";
import { router } from "tinro";
import NoComponentLoaded from "../base/NoComponentLoaded.svelte";
import { AuthService } from "@odit/lfk-client-js";
$: navOpen = false;
function logout() {
localForage.clear();
location.replace("/");
}
</script>
<style>
.collapsed_navigation {
transform: translateX(-100%);
}
@media (min-width: 768px) {
.collapsed_navigation {
transform: translateX(0px);
}
}
</style>
<section class="min-h-screen bg-gray-50">
<div
class:collapsed_navigation={!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 bg-gray-50">
<a href="/" class="flex items-center px-4 py-5">
<img src="/lfk-logo.png" alt="Logo" class="h-10" />
<h3 class="text-lg">Lauf für Kaya! Admin</h3>
</a>
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation">
<a
class:bg-gray-100={$router.path === '/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
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" />
</svg>
<span>{$_('dashboard-title')}</span>
</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
<a
class:bg-gray-100={$router.path.includes('/orgs/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/orgs/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="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>
<span>{$_('orgs')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')}
<a
class:bg-gray-100={$router.path === '/users/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/users/">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 14v8H4a8 8 0 018-8zm0-1a6 6 0 110-12 6 6 0 010 12zm2.6 5.81a3.51 3.51 0 010-1.62l-1-.57 1-1.74 1 .58a3.5 3.5 0 011.4-.82V13.5h2v1.15a3.5 3.5 0 011.4.8l1-.57 1 1.74-1 .57a3.51 3.51 0 010 1.62l1 .57-1 1.74-1-.58a3.5 3.5 0 01-1.4.82v1.14h-2v-1.15a3.5 3.5 0 01-1.4-.8l-1 .57-1-1.74 1-.57zM18 17a1 1 0 100 2 1 1 0 000-2z" /></svg>
<span>{$_('users')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')}
<a
class:bg-gray-100={$router.path === '/groups/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/groups/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('user-groups')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
<a
class:bg-gray-100={$router.path === '/runners/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/runners/">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
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>
<span>{$_('runners')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
<a
class:bg-gray-100={$router.path === '/teams/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/teams/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
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>
<span>{$_('teams')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
<a
class:bg-gray-100={$router.path.includes('/donors/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donors/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg>
<span>{$_('donors')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
<a
class:bg-gray-100={$router.path.includes('/donations/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donations/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg>
<span>{$_('donations')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')}
<a
class:bg-gray-100={$router.path === '/tracks/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/tracks/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
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>
<span>{$_('tracks')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
<a
class:bg-gray-100={$router.path === '/cards/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/cards/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="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>
<span>{$_('cards')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')}
<a
class:bg-gray-100={$router.path === '/scans/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/scans/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg>
<span>Scans</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')}
<a
class:bg-gray-100={$router.path === '/contacts/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/contacts/">
<svg
fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg>
<span>{$_('contacts')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')}
<a
class:bg-gray-100={$router.path === '/scanstations/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/scanstations/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
<span>{$_('scanstations')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:GET')}
<a
class:bg-gray-100={$router.path === '/statsclients/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/statsclients/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
<span>{$_('statsclients')}</span>
</a>
{/if}
<a
class:bg-gray-100={$router.path === '/settings/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/settings/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
fill-rule="evenodd"
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"
clip-rule="evenodd" />
</svg>
<span>{$_('settings')}</span>
</a>
<a
class:bg-gray-100={$router.path === '/about/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/about/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
<path d="M12 16v-4M12 8h.01" /></svg>
<span>{$_('about')}</span>
</a>
<span
tabindex="0"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
on:click={() => {
AuthService.authControllerLogout();
logout();
}}>
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
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>
<span>{$_('logout')}</span>
</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

@@ -1,23 +1,224 @@
<script>
import { _ } from "svelte-i18n";
import StatCards from "./StatCards.svelte";
import { StatsService } from "@odit/lfk-client-js";
import StatCards from "./StatCard.svelte";
import store from "../../store";
import StatCard from "./StatCard.svelte";
let navOpen = false;
const stats_promise = StatsService.statsControllerGet();
</script>
<div
class="p-5 overflow-x-hidden"
on:click={() => {
navOpen = false;
}}>
}}
>
<h1 class="text-3xl leading-tight">
<span class="font-extrabold">{$_('dashboard-title')}</span>
<span class="font-extrabold">{$_("dashboard-title")}</span>
<span>
-
{$_('dashboard-greeting')},
<span
class="text-blue-500">{store.state.jwtinfo.userdetails.firstname}</span>
👋</span>
{$_("dashboard-greeting")},
<span class="text-blue-500"
>{store.state.jwtinfo.userdetails.firstname}
{store.state.jwtinfo.userdetails.lastname}</span
></span
>
</h1>
<StatCards />
<h1>{$_("general-stats")}</h1>
{#await stats_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("stats-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then stats}
<div
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 2xl:grid-cols-6 gap-4"
>
<StatCard
title={$_("runners")}
value={stats.total_runners}
href="/runners/"
>
<svg
height="24"
width="24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path d="M0 0h24v24H0z" fill="none" />
<path
d="M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z"
/></svg
>
</StatCard>
<StatCard
title={$_("total-scans")}
value={stats.total_scans}
href="/scans/"
>
<svg
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z"
/></svg
>
</StatCard>
<StatCard
title={$_("total-donors")}
value={stats.total_donors}
href="/donors/"
>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"
/></svg
>
</StatCard>
<StatCard
title={$_("total-donation-count")}
value={stats.total_donations}
href="/donations/"
>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</StatCard>
<StatCard
title={$_("average-donation")}
value={`${(stats.average_donation / 100).toFixed(2)} €`}
href="/donations/"
>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</StatCard>
<StatCard
title={$_("total-donations")}
value={`${(stats.total_donation / 100).toFixed(2)} €`}
href="/donations/"
>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
fill="currentColor"
width="24"
><path d="M0 0h24v24H0z" fill="none" />
<path
d="M15 18.5A6.48 6.48 0 019.24 15H15v-2H8.58c-.05-.33-.08-.66-.08-1s.03-.67.08-1H15V9H9.24A6.491 6.491 0 0115 5.5c1.61 0 3.09.59 4.23 1.57L21 5.3A8.955 8.955 0 0015 3c-3.92 0-7.24 2.51-8.48 6H3v2h3.06a8.262 8.262 0 000 2H3v2h3.52c1.24 3.49 4.56 6 8.48 6 2.31 0 4.41-.87 6-2.3l-1.78-1.77c-1.13.98-2.6 1.57-4.22 1.57z"
/></svg
>
</StatCard>
<StatCard
title={$_("total-distance")}
value={`${stats.total_distance / 1000} km`}
href="#"
>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
height="24"
width="24"
><path d="M0 0h24v24H0z" fill="none" />
<path
d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z"
/></svg
>
</StatCard>
<StatCard
title={$_("average-distance")}
value={`${(stats.average_distance / 1000).toFixed(2)} km`}
href="#"
>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
height="24"
width="24"
><path d="M0 0h24v24H0z" fill="none" />
<path
d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z"
/></svg
>
</StatCard>
<StatCard
title={$_("count_teams")}
value={stats.total_teams}
href="/teams/"
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"
><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg
>
</StatCard>
<StatCard
title={$_("count_organizations")}
value={stats.total_orgs}
href="/orgs/"
>
<svg
height="24"
fill="currentColor"
width="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M17 11V3H7v4H3v14h8v-4h2v4h8V11h-4zM7 19H5v-2h2v2zm0-4H5v-2h2v2zm0-4H5V9h2v2zm4 4H9v-2h2v2zm0-4H9V9h2v2zm0-4H9V5h2v2zm4 8h-2v-2h2v2zm0-4h-2V9h2v2zm0-4h-2V5h2v2zm4 12h-2v-2h2v2zm0-4h-2v-2h2v2z"
/></svg
>
</StatCard>
</div>
{: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}
</div>

View File

@@ -0,0 +1,22 @@
<script>
import { _ } from "svelte-i18n";
export let href = "#"
export let title = "";
export let value = "";
</script>
<a href={href}>
<div
class="p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{title}
</div>
<div class="text-xl font-bold">{value}</div>
</div>
<slot></slot>
</div>
</div>
</a>

View File

@@ -1,165 +0,0 @@
<script>
import { StatsService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
const stats_promise = StatsService.statsControllerGet();
</script>
<!-- -->
<h1>{$_('general-stats')}</h1>
{#await stats_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('stats-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then stats}
<div
class="flex flex-col lg:flex-row w-full lg:space-x-2 space-y-2 lg:space-y-0 mb-2 lg:mb-4">
<a href="/runners/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('runners')}
</div>
<div class="text-xl font-bold">{stats.total_runners}</div>
</div>
<svg
height="24"
width="24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z" /></svg>
</div>
</div>
</a>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-scans')}
</div>
<div class="text-xl font-bold">{stats.total_scans}</div>
</div><svg
stroke="currentColor"
fill="currentColor"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"><polyline
points="22 12 18 12 15 21 9 3 6 12 2 12" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-donations')}
</div>
<div class="text-xl font-bold">{stats.total_donation}</div>
</div><svg
xmlns="http://www.w3.org/2000/svg"
height="24"
fill="currentColor"
width="24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M15 18.5A6.48 6.48 0 019.24 15H15v-2H8.58c-.05-.33-.08-.66-.08-1s.03-.67.08-1H15V9H9.24A6.491 6.491 0 0115 5.5c1.61 0 3.09.59 4.23 1.57L21 5.3A8.955 8.955 0 0015 3c-3.92 0-7.24 2.51-8.48 6H3v2h3.06a8.262 8.262 0 000 2H3v2h3.52c1.24 3.49 4.56 6 8.48 6 2.31 0 4.41-.87 6-2.3l-1.78-1.77c-1.13.98-2.6 1.57-4.22 1.57z" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-distance')}
</div>
<div class="text-xl font-bold">
{stats.total_distance / 1000}
km
</div>
</div>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
height="24"
width="24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z" /></svg>
</div>
</div>
</div>
<a href="/teams/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('count_teams')}
</div>
<div class="text-xl font-bold">{stats.total_teams}</div>
</div>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"><path
d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>
</div>
</div>
</a>
<a href="/orgs/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('count_organizations')}
</div>
<div class="text-xl font-bold">{stats.total_orgs}</div>
</div>
<svg
height="24"
fill="currentColor"
width="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M17 11V3H7v4H3v14h8v-4h2v4h8V11h-4zM7 19H5v-2h2v2zm0-4H5v-2h2v2zm0-4H5V9h2v2zm4 4H9v-2h2v2zm0-4H9V9h2v2zm0-4H9V5h2v2zm4 8h-2v-2h2v2zm0-4h-2V9h2v2zm0-4h-2V5h2v2zm4 12h-2v-2h2v2zm0-4h-2v-2h2v2z" /></svg>
</div>
</div>
</a>
</div>
{: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}

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
DonationService,
DonorService,
@@ -9,8 +9,10 @@
} from "@odit/lfk-client-js";
import Select from "svelte-select";
import Toastify from "toastify-js";
import { is_promise } from "svelte/internal";
import { createEventDispatcher } from "svelte";
export let modal_open;
export let current_donations;
const dispatch = createEventDispatcher();
const getDonorLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterDonors = (label, filterText, option) =>
@@ -24,6 +26,7 @@
$: donors = [];
$: runners = [];
$: is_fixed = false;
$: is_paid = false;
DonorService.donorControllerGetAll().then((val) => {
donors = val.map((r) => {
return { label: getDonorLabel(r), value: r };
@@ -57,14 +60,18 @@
let amount_cent = Math.floor(amount_input * 100);
processed_last_submit = false;
const toast = Toastify({
text: "adding donation",
text: $_("adding-donation"),
duration: -1,
}).showToast();
if (is_fixed) {
let postdata = {
donor,
amount: amount_cent,
paidAmount: 0,
};
if (is_paid) {
postdata.paidAmount = amount_cent;
}
DonationService.donationControllerPostFixed(postdata)
.then((result) => {
donor = donors[0].id || 0;
@@ -73,12 +80,11 @@
modal_open = false;
//
Toastify({
text: "donation_added",
text: $_("donation_added"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donations.push(result);
current_donations = current_donations;
dispatch("created", { donations: [result] });
})
.catch((err) => {
//
@@ -102,12 +108,11 @@
modal_open = false;
//
Toastify({
text: "donation_added",
text: $_("donation_added"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donations.push(result);
current_donations = current_donations;
dispatch("created", { donations: [result] });
})
.catch((err) => {
//
@@ -122,8 +127,209 @@
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{#if is_fixed}
{$_("create-a-new-fixed-donation")}
{:else}{$_("create-a-new-distance-donation")}{/if}
</h3>
<label class="content-center align-middle object-center">
<span class="ml-2 text-base" class:text-gray-300={is_fixed}
>{$_("distance-donation")}</span
>
<input
class="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"
bind:checked={is_fixed}
/>
<span class="ml-2 text-base" class:text-gray-300={!is_fixed}
>{$_("fixed-donation")}</span
>
</label>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-nessecary-information-to-create-a-new-donation"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700"
>{$_("donor")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={donors}
showChevron={true}
placeholder={$_("search-for-donor-name-or-id")}
noOptionsMessage={$_("no-donors-found")}
on:select={(selectedValue) =>
(donor = selectedValue.detail.value.id)}
on:clear={() => (donors = null)}
/>
</div>
{#if !is_fixed}
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700"
>{$_("runner")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
on:select={(selectedValue) =>
(runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)}
/>
</div>
{/if}
<div class="col-span-6">
<label
for="donation_amount_eur"
class="block text-sm font-medium text-gray-700"
>
{#if !is_fixed}
{$_("amount-per-kilometer")}
{:else}{$_("donation-amount")}{/if}</label
>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="2.00"
/>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
></span
>
</div>
{#if !is_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("donation-amount-must-be-greater-that-0-00eur")}
</span>
{/if}
</div>
{#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 class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("create")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}
<style>
input:before {
.toggle:before {
content: "";
position: absolute;
width: 1.25rem;
@@ -137,159 +343,12 @@
transition: 0.2s ease-in-out;
}
input:checked {
.toggle:checked {
/* @apply: bg-indigo-400; */
background-color: #7f9cf5;
}
input:checked:before {
.toggle:checked:before {
left: 1.25rem;
}
</style>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{#if is_fixed}
{$_('create-a-new-fixed-donation')}
{:else}{$_('create-a-new-distance-donation')}{/if}
</h3>
<label class="content-center align-middle object-center">
<span
class="ml-2 text-base"
class:text-gray-300={is_fixed}>{$_('distance-donation')}</span>
<input
class="relative w-10 h-5 transition-all duration-200 ease-in-out bg-gray-400 rounded-full shadow-inner outline-none appearance-none align-middle"
type="checkbox"
bind:checked={is_fixed} />
<span
class="ml-2 text-base "
class:text-gray-300={!is_fixed}>{$_('fixed-donation')}</span>
</label>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('please-provide-the-nessecary-information-to-create-a-new-donation')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700">{$_('donor')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)}
items={donors}
showChevron={true}
placeholder={$_('search-for-donor-name-or-id')}
noOptionsMessage={$_('no-donors-found')}
on:select={(selectedValue) => (donor = selectedValue.detail.value.id)}
on:clear={() => (donors = null)} />
</div>
{#if !is_fixed}
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700">{$_('runner')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_('search-for-runner-by-name-or-id')}
noOptionsMessage={$_('no-runners-found')}
on:select={(selectedValue) => (runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)} />
</div>
{/if}
<div class="col-span-6">
<label
for="donation_amount_eur"
class="block text-sm font-medium text-gray-700">
{#if !is_fixed}
{$_('amount-per-kilometer')}
{:else}{$_('donation-amount')}{/if}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="2.00" />
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"></span>
</div>
{#if !is_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('donation-amount-must-be-greater-that-0-00eur')}
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,221 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { DonationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let payment_modal_open = false;
export let original_data = {};
export let paid_amount_input = 0;
const dispatch = createEventDispatcher();
$: 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();
const editable = Object.assign({}, original_data);
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) => {
payment_modal_open = false;
//
Toastify({
text: $_("donation-updated"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
dispatch("created", { donation: response });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
} else {
DonationService.donationControllerPutFixed(original_data.id, editable)
.then((result) => {
payment_modal_open = false;
//
Toastify({
text: $_("donation-updated"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
dispatch("created", { donation: response });
})
.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: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

@@ -0,0 +1,122 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_donation = {
id: 0,
runner: {
firstname: "",
lastname: "",
},
donor: {
firstname: "",
lastname: "",
},
};
const dispatch = createEventDispatcher();
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
async function submit() {
dispatch("delete", { id: delete_donation.id });
modal_open = false;
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("confirm-delete")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_("please-confirm-the-deletion-of-donation")}
</p>
</div>
<div class="w-full">
<span class="inline-block"
><b>{$_("donor")}</b>: {delete_donation.donor.firstname}
{delete_donation.donor.lastname}</span
>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={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"
>
{$_("delete")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -20,6 +20,8 @@
$: current_runners = [];
$: amount_input = 0;
$: is_amount_valid = amount_input > 0;
$: paid_amount_input = 0;
$: is_paid_amount_valid = paid_amount_input > 0;
$: is_everything_set =
editable.donor != null &&
((original_data.responseType == "DISTANCEDONATION" &&
@@ -30,15 +32,17 @@
(original_data.responseType == "DISTANCEDONATION" &&
!(Math.floor(amount_input * 100) === original_data.amountPerDistance)) ||
(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;
const promise = DonationService.donationControllerGetOne(
params.donationid
).then((data) => {
data_loaded = true;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
original_data = Object.assign({}, data);
editable = Object.assign({}, original_data);
paid_amount_input = data.paidAmount / 100;
if (data.responseType == "DISTANCEDONATION") {
amount_input = data.amountPerDistance / 100;
RunnerService.runnerControllerGetAll().then((val) => {
@@ -66,10 +70,11 @@
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: "Donation is being updated",
text: $_('updating-donation'),
duration: 2500,
}).showToast();
let postdata = {};
editable.paidAmount = paid_amount_input*100;
if (original_data.responseType === "DISTANCEDONATION") {
editable.amountPerDistance = Math.floor(amount_input * 100);
postdata = Object.assign(postdata, editable);
@@ -83,7 +88,7 @@
Object.assign(original_data, editable);
original_data = original_data;
Toastify({
text: "updated donation",
text: $_('donation-updated'),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
@@ -98,7 +103,7 @@
Object.assign(original_data, editable);
original_data = original_data;
Toastify({
text: "updated donation",
text: $_('donation-updated'),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
@@ -112,7 +117,7 @@
DonationService.donationControllerRemove(original_data.id, false)
.then((resp) => {
Toastify({
text: "Donation delete",
text: $_('donation-deleted'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
@@ -219,7 +224,24 @@
<span>{(editable.amount / 100)
.toFixed(2)
.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>
<br>
<div class=" w-full">
<label
for="donor"
@@ -232,7 +254,7 @@
placeholder={$_('search-for-donor-name-or-id')}
noOptionsMessage={$_('no-donors-found')}
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)} />
</div>
{#if original_data.responseType == 'DISTANCEDONATION'}
@@ -280,6 +302,39 @@
</span>
{/if}
</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>
{:catch error}
<PromiseError {error} />

View File

@@ -0,0 +1,18 @@
<script>
import { _ } from "svelte-i18n";
export let donor;
</script>
{#if !donor || donor.firstname == 0}
{$_("donor-has-no-associated-donations")}
{:else}
<div class="flex items-center">
<a
href="../donors/{donor.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{donor.firstname}
{#if donor.middlename}{donor.middlename}{/if}
{donor.lastname}</a
>
</div>
{/if}

View File

@@ -0,0 +1,18 @@
<script>
import { _ } from "svelte-i18n";
export let runner;
</script>
{#if !runner || runner.firstname == 0}
{$_("fixed-donation")}
{:else}
<div class="text-sm font-medium text-gray-900">
<a
href="../runners/{runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{runner.firstname}
{#if runner.middlename}{runner.middlename}{/if}
{runner.lastname}</a
>
</div>
{/if}

View File

@@ -0,0 +1,16 @@
<script>
import { _ } from "svelte-i18n";
export let status;
</script>
{#if 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}

View File

@@ -0,0 +1,21 @@
<script>
import { _ } from "svelte-i18n";
import TableActions from "../shared/TableActions.svelte";
export let detailsLink;
export let detailsAction;
export let deleteEnabled;
export let deleteAction;
export let paymentAction;
</script>
<button
on:click={paymentAction}
class="text-[#025a21] hover:text-green-900 mr-4">{$_("enter-payment")}</button
>
<TableActions
bind:detailsAction
bind:detailsLink
bind:deleteAction
bind:deleteEnabled
/>

View File

@@ -5,25 +5,33 @@
import DonationsOverview from "./DonationsOverview.svelte";
$: current_donations = [];
export let modal_open = false;
let addDonations;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('donations')}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:CREATE')}
{$_("donations")}
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('add-donation')}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("add-donation")}
</button>
{/if}
</span>
<DonationsOverview bind:current_donations />
<DonationsOverview bind:current_donations bind:addDonations />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:CREATE')}
<AddDonationModal bind:current_donations bind:modal_open />
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")}
<AddDonationModal
on:created={(event) => {
console.log(event)
addDonations(event.detail.donations);
}}
bind:modal_open
/>
{/if}

View File

@@ -5,8 +5,8 @@
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full" style="height:15rem" src={donations_empty} alt="" />
<span class="font-bold">There are no donations yet</span><br />
<span>add your fist donation</span>
<img class="m-auto" style="height:15rem" src={donations_empty} alt="" />
<span class="font-bold">{$_('there-are-no-donations-yet')}</span><br />
<span>{$_('add-your-fist-donation')}</span>
</p>
</div>

View File

@@ -1,202 +1,279 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { DonationService, DonorService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import { DonationService } from "@odit/lfk-client-js";
import store from "../../store";
import Toastify from "toastify-js";
import DonationsEmptyState from "./DonationsEmptyState.svelte";
import AddDonationPaymentModal from "./AddDonationPaymentModal.svelte";
import { onMount } from "svelte";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { writable } from "svelte/store";
import TableBottom from "../shared/TableBottom.svelte";
import InputElement from "../shared/InputElement.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import DonationDonor from "./DonationDonor.svelte";
import DonationRunner from "./DonationRunner.svelte";
import DonationStatus from "./DonationStatus.svelte";
import DonationTableAction from "./DonationTableAction.svelte";
import DeleteDonationModal from "./DeleteDonationModal.svelte";
$: searchvalue = "";
$: active_deletes = [];
$: active_edits = [];
$: selectedDonations =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: dataLoaded = false;
export let current_donations = [];
const donations_promise = DonationService.donationControllerGetAll().then(
(val) => {
current_donations = val;
}
);
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
export const addDonations = (donations) => {
current_donations = current_donations.concat(...donations);
options.update((options) => ({
...options,
data: current_donations,
}));
};
//Section table
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "donor",
header: () => $_("donor"),
cell: (info) => {
return renderComponent(DonationDonor, { donor: info.getValue() });
},
filterFn: `includesString`,
},
{
accessorKey: "runner",
header: () => $_("runner"),
cell: (info) => {
return renderComponent(DonationRunner, { runner: info.getValue() });
},
filterFn: `includesString`,
},
{
accessorKey: "amountPerDistance",
header: () => $_("amount-per-kilometer"),
cell: (info) => {
if (!info.getValue()) {
return $_("fixed-donation");
}
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })} €`;
},
enableColumnFilter: false,
},
{
accessorKey: "amount",
header: () => $_("donation-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })} €`;
},
enableColumnFilter: false,
},
{
accessorKey: "paidAmount",
header: () => $_("total-paid-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })} €`;
},
enableColumnFilter: false,
},
{
accessorKey: "status",
header: () => $_("status"),
cell: (info) => {
return renderComponent(DonationStatus, { status: info.getValue() });
},
enableColumnFilter: false,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(DonationTableAction, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_deletes = current_donations.filter(
(r) => r.id == info.row.original.id
);
},
paymentAction: () => {
active_edits = current_donations.filter(
(r) => r.id == info.row.original.id
);
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes(
"DONATION:DELETE"
),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
async function deleteDonation(delete_donation_id) {
await DonationService.donationControllerRemove(delete_donation_id, true);
current_donations = current_donations.filter(
(r) => r.id !== delete_donation_id
);
options.update((options) => ({
...options,
data: current_donations,
}));
Toastify({
text: $_("donation-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
onMount(async () => {
let page = 0;
while (page >= 0) {
const donations = await DonationService.donationControllerGetAll(
page,
500
);
if (donations.length == 0) {
page = -2;
}
current_donations = current_donations.concat(...donations);
options.update((options) => ({
...options,
data: current_donations,
}));
dataLoaded = true;
page++;
}
console.log("All donations loaded");
});
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
{#await donations_promise}
<AddDonationPaymentModal
original_data={active_edits[0]}
payment_modal_open={active_edits.length > 0}
paid_amount_input={(active_edits[0]?.paidAmount || 0) / 100}
on:created={(event) => {
current_donations[
current_donations.findIndex((d) => d.id === event.detail.donation.id)
].paidAmount = event.detail.donation.paidAmount;
options.update((options) => ({
...options,
data: current_donations,
}));
}}
/>
<DeleteDonationModal
delete_donation={active_deletes[0]}
modal_open={active_deletes.length > 0}
on:delete={(event) => {
deleteDonation(event.detail.id);
}}
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:GET")}
{#if !dataLoaded}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">donations are being loaded</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
role="alert"
>
<p class="font-bold">{$_("donations-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then}
{#if current_donations.length === 0}
<DonationsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('donor')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('runner')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('amount-per-kilometer')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('donation-amount')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
{:else if current_donations.length === 0}
<DonationsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_("datatable.search")}
aria-label={$_("datatable.search")}
class="mb-4"
/>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<table class="w-full">
<thead>
{#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<InputElement
type="checkbox"
checked={$table.getIsAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()}
/>
</th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_donations as donation}
{#if donation.donor.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donation.donor.middlename
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donation.donor.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donation.runner?.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donation.runner?.middlename
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donation.runner?.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(donation.id)}
<tr data-rowid="donation_{donation.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<a
href="../donors/{donation.donor.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{donation.donor.firstname}
{donation.donor.middlename || ''}
{donation.donor.lastname}</a>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donation.runner}
<div class="text-sm font-medium text-gray-900">
<a
href="../runners/{donation.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{donation.runner.firstname}
{donation.runner.middlename || ''}
{donation.runner.lastname}</a>
</div>
{:else}
<div class="text-sm font-medium text-gray-900">
{$_('fixed-donation')}
</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donation.amountPerDistance}
<div class="text-sm font-medium text-gray-900">
{(donation.amountPerDistance / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}
</div>
{:else}
<div class="text-sm font-medium text-gray-900">
{$_('fixed-donation')}
</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
{(donation.amount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}
</div>
</td>
{#if active_deletes[donation.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[donation.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
DonationService.donationControllerRemove(donation.id, false).then(
(resp) => {
current_donations = current_donations.filter(
(obj) => obj.id !== donation.id
);
Toastify({
text: 'Donation deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{donation.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:DELETE')}
<button
on:click={() => {
active_deletes[donation.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
{/each}
</thead>
<tbody>
{#each $table.getRowModel().rows as row}
<tr>
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement
type="checkbox"
checked={row.getIsSelected()}
on:change={() => row.toggleSelected()}
/>
</td>
{#each row.getVisibleCells() as cell}
<td>
<svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
{/await}
<div class="h-2" />
<TableBottom {table} {selected} />
{/if}
{/if}

View File

@@ -1,15 +1,13 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
DonorService
} from "@odit/lfk-client-js";
import { DonorService } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail";
import isMobilePhone from "validator/es/lib/isMobilePhone";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let modal_open;
export let current_donors;
let firstname_input;
let lastname_input;
let middlename_input;
@@ -19,6 +17,7 @@
let address_input2;
let address_zipcode;
let address_city;
const dispatch = createEventDispatcher();
function focus(el) {
el.focus();
}
@@ -75,7 +74,7 @@
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_('donor-is-being-added'),
text: $_("donor-is-being-added"),
duration: -1,
}).showToast();
let address = {};
@@ -92,7 +91,7 @@
firstname: firstname_input_value,
lastname: lastname_input_value,
address,
receiptNeeded: address_checked
receiptNeeded: address_checked,
};
if (middlename_input_value) {
postdata.middlename = middlename_input_value;
@@ -112,12 +111,11 @@
modal_open = false;
//
Toastify({
text: $_('donor-added'),
text: $_("donor-added"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donors.push(result);
current_donors = current_donors;
dispatch("created", { donors: [result] });
})
.catch((err) => {
//
@@ -134,58 +132,70 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
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" />
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
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">
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" />
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg>
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-donor')}
{$_("create-a-new-donor")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('please-provide-the-nessecary-information-to-add-a-new-donor')}
{$_(
"please-provide-the-nessecary-information-to-add-a-new-donor"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700">{$_('first-name')}</label>
class="block text-sm font-medium text-gray-700"
>{$_("first-name")}</label
>
<input
use:focus
autocomplete="off"
placeholder={$_('first-name')}
placeholder={$_("first-name")}
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
@@ -193,34 +203,41 @@
bind:this={firstname_input}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("first-name-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700">{$_('middle-name')}</label>
class="block text-sm font-medium text-gray-700"
>{$_("middle-name")}</label
>
<input
autocomplete="off"
placeholder={$_('middle-name')}
placeholder={$_("middle-name")}
bind:value={middlename_input_value}
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label
for="lastname"
class="block text-sm font-medium text-gray-700">{$_('last-name')}</label>
class="block text-sm font-medium text-gray-700"
>{$_("last-name")}</label
>
<input
autocomplete="off"
placeholder="{$_('last-name')}"
placeholder={$_("last-name")}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
@@ -228,21 +245,25 @@
bind:this={lastname_input}
type="text"
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("last-name-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="phone"
class="block text-sm font-medium text-gray-700">{$_('phone')}</label>
class="block text-sm font-medium text-gray-700"
>{$_("phone")}</label
>
<input
autocomplete="off"
placeholder={$_('phone')}
placeholder={$_("phone")}
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
@@ -250,21 +271,27 @@
bind:this={phone_input}
type="tel"
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{@html $_(
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number"
)}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="email"
class="block text-sm font-medium text-gray-700">{$_('e-mail-adress')}</label>
class="block text-sm font-medium text-gray-700"
>{$_("e-mail-adress")}</label
>
<input
autocomplete="off"
placeholder={$_('e-mail-adress')}
placeholder={$_("e-mail-adress")}
class:border-red-500={!isEmailValidOrEmpty}
class:focus:border-red-500={!isEmailValidOrEmpty}
class:focus:ring-red-500={!isEmailValidOrEmpty}
@@ -272,11 +299,13 @@
bind:this={email_input}
type="email"
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isEmailValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-email-is-required')}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-email-is-required")}
</span>
{/if}
</div>
@@ -287,19 +316,22 @@
id="comments"
name="comments"
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">{$_('receipt-needed')}</label>
<label for="comments" class="font-medium text-gray-700"
>{$_("receipt-needed")}</label
>
</div>
</div>
{#if address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700">{$_('address')}</label>
class="block text-sm font-medium text-gray-700"
>{$_("address")}</label
>
<input
autocomplete="off"
placeholder="Address"
@@ -310,34 +342,41 @@
bind:this={address_input1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
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')}
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>
class="block text-sm font-medium text-gray-700"
>{$_("apartment-suite-etc")}</label
>
<input
autocomplete="off"
placeholder={$_('apartment-suite-etc')}
placeholder={$_("apartment-suite-etc")}
bind:value={address_input2_value}
bind:this={address_input2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
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>
class="block text-sm font-medium text-gray-700"
>{$_("zip-postal-code")}</label
>
<input
autocomplete="off"
placeholder={$_('zip-postal-code')}
placeholder={$_("zip-postal-code")}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
@@ -345,18 +384,22 @@
bind:this={address_zipcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
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')}
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>
class="block text-sm font-medium text-gray-700"
>City</label
>
<input
autocomplete="off"
placeholder="City"
@@ -367,11 +410,13 @@
bind:this={address_city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
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')}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-city-is-required")}
</span>
{/if}
</div>
@@ -386,16 +431,18 @@
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("create")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
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>

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { DonorService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
@@ -13,60 +13,61 @@
dispatch("cancelDelete", { id: delete_donor.id });
}
function deleteDonor() {
DonorService.donorControllerRemove(
delete_donor.id,
true
)
.then((resp) => {
Toastify({
text: "Donor deleted",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {});
dispatch("delete", { id: delete_donor.id });
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
on:click_outside={cancelDelete}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
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" />
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-blue-600" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M9.33 11.5h2.17A4.5 4.5 0 0116 16H9v1h8v-1a5.58 5.58 0 00-.89-3H19a5 5 0 014.52 2.85A13.15 13.15 0 0113 21c-2.76 0-5.1-.59-7-1.63v-9.3a6.97 6.97 0 013.33 1.43zM5 19a1 1 0 01-1 1H2a1 1 0 01-1-1v-9a1 1 0 011-1h2a1 1 0 011 1v9zM18 5a3 3 0 110 6 3 3 0 010-6zm-7-3a3 3 0 110 6 3 3 0 010-6z"/></svg>
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" /><path
d="M9.33 11.5h2.17A4.5 4.5 0 0116 16H9v1h8v-1a5.58 5.58 0 00-.89-3H19a5 5 0 014.52 2.85A13.15 13.15 0 0113 21c-2.76 0-5.1-.59-7-1.63v-9.3a6.97 6.97 0 013.33 1.43zM5 19a1 1 0 01-1 1H2a1 1 0 01-1-1v-9a1 1 0 011-1h2a1 1 0 011 1v9zM18 5a3 3 0 110 6 3 3 0 010-6zm-7-3a3 3 0 110 6 3 3 0 010-6z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('attention')}
{$_("attention")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
'do-you-want-to-delete-this-donor-with-all-related-donations'
"do-you-want-to-delete-this-donor-with-all-related-donations"
)}
<br />
{$_('all-associated-donations-will-get-deleted-as-well')}
<br />
{$_("all-associated-donations-will-get-deleted-as-well")}
</p>
</div>
</div>
@@ -76,14 +77,16 @@
<button
on:click={deleteDonor}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('confirm-delete-donor-with-all-donations')}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("confirm-delete-donor-with-all-donations")}
</button>
<button
on:click={cancelDelete}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel-keep-donor')}
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel-keep-donor")}
</button>
</div>
</div>

View File

@@ -0,0 +1,14 @@
<script>
import { _ } from "svelte-i18n";
export let address;
</script>
{#if !address || !address.address1}
{$_("no-address")}
{:else}
{address.address1}<br />
<!-- {address.address2 || ''}<br /> -->
{address.postalcode}
{address.city}
{address.country}
{/if}

View File

@@ -193,6 +193,12 @@
<span>{(editable.donationAmount / 100)
.toFixed(2)
.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 />
<span class="font-medium text-gray-700">{$_('donations')}:</span>
{#if current_donations.filter((d) => d.donor.id == editable.id).length > 0}
@@ -201,7 +207,7 @@
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname}
{d.runner.middlename}
{d.runner.middlename || ""}
{d.runner.lastname}</a>
{:else}
<a

View File

@@ -0,0 +1,29 @@
<script>
import { _ } from "svelte-i18n";
export let donations;
</script>
{#if !donations || donations.length == 0}
{$_('donor-has-no-associated-donations')}
{:else}
{#each donations as donation}
{#if donation.responseType === "DISTANCEDONATION"}
<a
href="../donations/{donation.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1"
>{donation.runner.firstname}
{donation.runner.middlename || ""}
{donation.runner.lastname}</a
>
{:else}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1"
>{$_("fixed-donation")}:
{(d.amount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</a
>
{/if}
{/each}
{/if}

View File

@@ -5,25 +5,73 @@
import DonorsOverview from "./DonorsOverview.svelte";
$: current_donors = [];
export let modal_open = false;
let addDonors;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('donors')}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:CREATE')}
{$_("donors")}
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('add-donor')}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("add-donor")}
</button>
{/if}
{#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>
<DonorsOverview bind:current_donors />
<DonorsOverview bind:current_donors bind:addDonors />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:CREATE')}
<AddDonorModal bind:current_donors bind:modal_open />
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:CREATE")}
<AddDonorModal
on:created={(event) => {
addDonors(event.detail.donors);
}}
bind:modal_open
/>
{/if}

View File

@@ -1,212 +1,266 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { _ } from "svelte-i18n";
import { DonationService, DonorService } from "@odit/lfk-client-js";
import store from "../../store";
import DonorsEmptyState from "./DonorsEmptyState.svelte";
import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte";
import Toastify from "toastify-js";
import TableBottom from "../shared/TableBottom.svelte";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { writable } from "svelte/store";
import { onMount } from "svelte";
import InputElement from "../shared/InputElement.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import TableActions from "../shared/TableActions.svelte";
import DonorAddress from "./DonorAddress.svelte";
import DonorDonations from "./DonorDonations.svelte";
$: searchvalue = "";
$: active_deletes = [];
$: current_donations = [];
$: selectedDonors =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: dataLoaded = false;
let modal_open = false;
let delete_donor = {};
export let current_donors = [];
const donors_promise = DonorService.donorControllerGetAll().then((val) => {
current_donors = val;
export const addDonors = (donors) => {
current_donors = current_donors.concat(...donors);
options.update((options) => ({
...options,
data: current_donors,
}));
};
//Section table
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "name",
header: () => $_("name"),
cell: (info) => {
const d = info.row.original;
if (d.middlename) {
return `${d.firstname} ${d.middlename} ${d.lastname}`;
} else {
return `${d.firstname} ${d.lastname}`;
}
},
filterFn: `includesString`,
},
{
accessorKey: "address",
header: () => $_("contact-information"),
cell: (info) => {
return renderComponent(DonorAddress, { address: info.getValue() });
},
filterFn: `includesString`,
},
{
accessorKey: "sponsorings",
header: () => $_("sponsorings"),
cell: (info) => {
const donations = current_donations.filter(
(d) => d?.donor?.id == info.row.original.id
);
return renderComponent(DonorDonations, { donations });
},
enableColumnFilter: false,
},
{
accessorKey: "donationAmount",
header: () => $_("total-donation-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}€`;
},
enableColumnFilter: false,
},
{
accessorKey: "paidDonationAmount",
header: () => $_("total-paid-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}€`;
},
enableColumnFilter: false,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_deletes = current_donors.filter(
(r) => r.id == info.row.original.id
);
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes(
"DONOR:DELETE"
),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const donation_promise = DonationService.donationControllerGetAll().then(
(val) => {
current_donations = val;
}
);
const table = createSvelteTable(options);
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
}
onMount(async () => {
let page = 0;
while (page >= 0) {
const donors = await DonorService.donorControllerGetAll(page, 500);
const donations = await DonationService.donationControllerGetAll(
page,
500
);
if (donors.length == 0 && donations.length == 0) {
page = -2;
}
current_donors = current_donors.concat(...donors);
current_donations = current_donations.concat(...donors);
options.update((options) => ({
...options,
data: current_donors,
}));
dataLoaded = true;
page++;
}
console.log("All donors loaded");
});
</script>
<ConfirmDonorDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
active_deletes = active_deletes.filter((a) => a.id !== event.detail.id);
}}
bind:modal_open
bind:delete_donor />
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
{#await donors_promise && donation_promise}
on:delete={async (event) => {
await DonorService.donorControllerRemove(event.detail.id, true);
Toastify({
text: $_("donor-deleted"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donors = current_donors.filter((d) => d.id !== event.detail.id);
active_deletes = active_deletes.filter((a) => a.id !== event.detail.id);
options.update((options) => ({
...options,
data: current_donors,
}));
}}
modal_open={active_deletes.length > 0}
delete_donor={active_deletes[0]}
/>
{active_deletes.length}
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")}
{#if !dataLoaded}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('donors-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
role="alert"
>
<p class="font-bold">{$_("donors-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then}
{#if current_donors.length === 0}
<DonorsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('name')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('contact-information')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('donations')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('total-donation-amount')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
{:else if current_donors.length === 0}
<DonorsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_("datatable.search")}
aria-label={$_("datatable.search")}
class="mb-4"
/>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<table class="w-full">
<thead>
{#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<InputElement
type="checkbox"
checked={$table.getIsAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()}
/>
</th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_donors as donor}
{#if donor.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donor.middlename
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donor.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(donor.id)}
<tr data-rowid="donor_{donor.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{donor.firstname}
{donor.middlename || ''}
{donor.lastname}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donor.email}
<div class="text-sm text-gray-500">{donor.email}</div>
{/if}
{#if donor.phone}
<div class="text-sm text-gray-500">{donor.phone}</div>
{/if}
{#if donor.address.address1 !== null}
{donor.address.address1}<br />
{donor.address.address2 || ''}<br />
{donor.address.postalcode}
{donor.address.city}
{donor.address.country}
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if current_donations.filter((d) => d.donor.id == donor.id).length > 0}
{#each current_donations.filter((o) => o.donor.id == donor.id) as d}
{#if d.responseType === 'DISTANCEDONATION'}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname}
{d.runner.middlename}
{d.runner.lastname}</a>
{:else}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1">{$_('fixed-donation')}:
{(d.amount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</a>
{/if}
{/each}
{:else}{$_('donor-has-no-associated-donations')}{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{(donor.donationAmount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}
</td>
{#if active_deletes[donor.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[donor.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
DonorService.donorControllerRemove(donor.id, false)
.then((resp) => {
current_donors = current_donors.filter((obj) => obj.id !== donor.id);
Toastify({
text: 'Donor deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_donor = donor;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{donor.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:DELETE')}
<button
on:click={() => {
active_deletes[donor.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
{/each}
</thead>
<tbody>
{#each $table.getRowModel().rows as row}
<tr>
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement
type="checkbox"
checked={row.getIsSelected()}
on:change={() => row.toggleSelected()}
/>
</td>
{#each row.getVisibleCells() as cell}
<td>
<svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
{/await}
<div class="h-2" />
<TableBottom {table} {selected} />
{/if}
{/if}

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
export let modal_open;
(function () {
document.onkeydown = function (e) {
@@ -25,7 +25,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
@@ -189,34 +189,5 @@
</li>
</ul>
</div>
<h2 class="mt-4 text-4xl font-display font-semibold md:text-5xl">
{$_('faq')}
</h2>
<div class="mt-6 border-t-2 border-gray-100 pt-10">
<dl class="md:grid md:grid-cols-2 md:gap-8">
<div>
<div>
<dt class="text-lg leading-6 font-medium">Q</dt>
<dd class="mt-2">
<p class="text-base text-gray-500">A</p>
</dd>
</div>
</div>
<div class="mt-12 sm:mt-0">
<div id="team-pricing">
<dt class="text-lg leading-6 font-medium">Q</dt>
<dd class="mt-2">
<p class="text-base text-gray-500">A</p>
</dd>
</div>
<div class="mt-12">
<dt class="text-lg leading-6 font-medium">Q</dt>
<dd class="mt-2">
<p class="text-base text-gray-500">A</p>
</dd>
</div>
</div>
</dl>
</div>
</div>
</div>

View File

@@ -33,6 +33,12 @@
rel="noopener, noreferrer"
href="https://git.odit.services/lfk/frontend/src/tag/{releaseinfo}">{releaseinfo}</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="/imprint">{$_('imprint')}</a>

View File

@@ -6,13 +6,15 @@
let html = "";
async function load() {
let md = await fetch("/imprint_" + getLocaleFromNavigator() + ".md");
if((await md.text()).includes("<meta")){
let text = (await md.text()).toString();
if(text.includes("<meta")){
md.ok=false
}
if (!md.ok) {
md = await fetch("/imprint_en.md");
text = await md.text();
}
html = marked(await md.text());
html = marked(text);
}
const promise = load();
</script>

View File

@@ -2,17 +2,19 @@
import { _, getLocaleFromNavigator } from "svelte-i18n";
import marked from "marked";
import Footer from "./Footer.svelte";
import * as css from "../base/simple.css";
// import * as css from "../base/simple.css?inline";
let html = "";
async function load() {
let md = await fetch("/privacy_" + getLocaleFromNavigator() + ".md");
if((await md.text()).includes("<meta")){
let text = (await md.text()).toString();
if(text.includes("<meta")){
md.ok=false
}
if (!md.ok) {
md = await fetch("/privacy_en.md");
text = await md.text();
}
html = marked(await md.text());
html = marked(text);
}
const promise = load();
</script>

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import Toastify from "toastify-js";
import { UserGroupService } from "@odit/lfk-client-js";
export let modal_open;
@@ -69,7 +69,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -30,7 +30,7 @@
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
class="mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let modal_open;
@@ -89,7 +89,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
@@ -19,7 +19,7 @@
)
.then((resp) => {
Toastify({
text: "Organization deleted",
text: $_('organization-deleted'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
@@ -32,7 +32,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div

View File

@@ -10,6 +10,10 @@
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select";
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;
$: address_valid_or_none =
(isAddress1Valid && iszipcodevalid && iscityvalid) ||
@@ -18,6 +22,9 @@
let original = "";
let original_object = {};
let contacts = [];
let valueCopy = null;
let areaDom;
let copied = false;
export let params;
$: editable = {};
$: contact = {};
@@ -26,7 +33,11 @@
$: isAddress1Valid = editable.address?.address1?.trim().length !== 0;
$: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0;
$: iscityvalid = editable.address?.city?.trim().length !== 0;
$: sponsoring_contracts_download_open = false;
$: sponsoring_contracts_show = true;
$: cards_show = true;
$: certificates_show = true;
$: generate_orgs = [original_object];
$: registrationLink = `${config.baseurl}/selfservice/register/${editable.registrationKey}`;
const getContactLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne(
@@ -60,14 +71,6 @@
});
let modal_open = false;
let delete_org = {};
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
function deleteOrganization() {
RunnerOrganizationService.runnerOrganizationControllerRemove(
original_object.id,
@@ -102,6 +105,7 @@
postdata
)
.then((resp) => {
editable.registrationKey = resp.registrationKey;
original_object = Object.assign({}, editable);
original = JSON.stringify(original_object);
Toastify({
@@ -114,58 +118,46 @@
} else {
}
}
export let import_modal_open = false;
async function generateSponsoringContract(locale) {
sponsoring_contracts_download_open = false;
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
original_object.id
);
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`https://dev.lauf-fuer-kaya.de/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
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();
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Sponsorings_" + original_object.name + ".pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
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;
</script>
{#if valueCopy != null}<textarea bind:this={areaDom}>{valueCopy}</textarea>{/if}
<ImportRunnerModal
on:cancelDelete={(event) => {
import_modal_open = false;
@@ -175,91 +167,45 @@
passed_orgs={[]}
passed_org={editable}
opened_from="OrgDetail"
bind:import_modal_open />
bind:import_modal_open
/>
<ConfirmOrgDeletion bind:modal_open bind:delete_org />
{#if data_loaded}
<section class="container p-5">
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original_object.name}
<span data-id="org_actions_${editable.id}">
<div id="sponsoring:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
sponsoring_contracts_download_open = !sponsoring_contracts_download_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-sponsoring-contracts')}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="sponsoring:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateSponsoringContract('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateSponsoringContract('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
<GenerateSponsoringContracts
bind:sponsoring_contracts_show
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")}
<button
on:click={() => {
import_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('import-runners')}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("import-runners")}
</button>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:DELETE')}
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:DELETE")}
{#if delete_triggered}
<button
on:click={deleteOrganization}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("confirm-delete")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
@@ -267,7 +213,9 @@
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-organization')}</button>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("delete-organization")}</button
>
{/if}
{/if}
{#if !delete_triggered}
@@ -276,7 +224,9 @@
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("save-changes")}</button
>
{/if}
</span>
</div>
@@ -295,12 +245,13 @@
class="h-3 w-3 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><path
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" /></svg>
xmlns="http://www.w3.org/2000/svg"
><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" /></svg
>
</li>
<li class="flex items-center">
<a class="mr-2" href="/">{$_('home')}</a><svg
<a class="mr-2" href="/">{$_("home")}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
@@ -310,24 +261,25 @@
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
</li>
<li class="mr-2 flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M21 20h2v2H1v-2h2V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v17zm-2 0V4H5v16h14zM8 11h3v2H8v-2zm0-4h3v2H8V7zm0 8h3v2H8v-2zm5 0h3v2h-3v-2zm0-4h3v2h-3v-2zm0-4h3v2h-3V7z" /></svg>
d="M21 20h2v2H1v-2h2V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v17zm-2 0V4H5v16h14zM8 11h3v2H8v-2zm0-4h3v2H8V7zm0 8h3v2H8v-2zm5 0h3v2h-3v-2zm0-4h3v2h-3v-2zm0-4h3v2h-3V7z"
/></svg
>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">{$_('organizations')}</a><svg
<a class="mr-2" href="./">{$_("organizations")}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
@@ -337,12 +289,10 @@
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
</li>
<li class="flex items-center">
<span class="mr-2">Org-Details #{params.orgid}</span>
@@ -352,133 +302,205 @@
</div>
</div>
<div class="text-sm w-full">
<label for="name" class="font-medium text-gray-700">{$_('name')}</label>
<label for="name" class="font-medium text-gray-700">{$_("name")}</label>
<input
autocomplete="off"
placeholder={$_('name')}
placeholder={$_("name")}
type="text"
bind:value={editable.name}
name="name"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div>
<div class="text-sm w-full">
<label
for="contact"
class="font-medium text-gray-700">{$_('contact')}</label>
<label for="contact" class="font-medium text-gray-700"
>{$_("contact")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => label
.toLowerCase()
.includes(
filterText.toLowerCase()
) || option.value.id
.toString()
.startsWith(filterText.toLowerCase())}
itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase())}
items={contacts}
showChevron={true}
placeholder={$_('no-contact-selected')}
noOptionsMessage={$_('no-contact-found')}
placeholder={$_("no-contact-selected")}
noOptionsMessage={$_("no-contact-found")}
bind:selectedValue={contact}
on:select={(selectedValue) => (editable.contact = selectedValue.detail.value)}
on:clear={() => (editable.contact = null)} />
on:select={(selectedValue) =>
(editable.contact = selectedValue.detail.value)}
on:clear={() => (editable.contact = null)}
/>
</div>
<!-- -->
<div class="flex items-start mt-2">
<div class="flex items-center h-5">
<input
bind:checked={editable.address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
<div>
<div class="flex items-start mt-2">
<div class="flex items-center h-5">
<input
bind:checked={editable.registrationEnabled}
id="toggle_selfservice_feature"
name="toggle_selfservice_feature"
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="toggle_selfservice_feature"
class="font-medium text-gray-700"
>{$_("selfservice-registration")}</label
>
</div>
</div>
<div class="ml-3 text-sm">
<label
for="comments"
class="font-medium text-gray-700">{$_('address')}</label>
<div>
{#if editable.registrationEnabled}
<div class="text-sm w-full">
<button 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>
</button>
{#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="toggle_address_checkbox"
name="toggle_address_checkbox"
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="toggle_address_checkbox"
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>
{#if editable.address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700">{$_('address')}</label>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('address-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label>
<input
autocomplete="off"
placeholder={$_('apartment-suite-etc')}
bind:value={editable.address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label>
<input
autocomplete="off"
placeholder={$_('zip-postal-code')}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-zipcode-postal-code-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700">{$_('city')}</label>
<input
autocomplete="off"
placeholder={$_('city')}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-city-is-required')}
</span>
{/if}
</div>
{/if}
</section>
{:else}
{#await promise}
{$_('organization-detail-is-being-loaded')}
{$_("organization-detail-is-being-loaded")}
{:catch error}
<PromiseError />
{/await}

View File

@@ -1,318 +1,219 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
let modal_open = false;
let delete_org = {};
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import store from "../../store";
import OrgsEmptyState from "./OrgsEmptyState.svelte";
import Toastify from "toastify-js";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
$: searchvalue = "";
$: active_deletes = [];
$: sponsoring_contracts_download_open = false;
export let current_organizations = [];
const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
current_organizations = val;
}
);
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
async function generateSponsoringContract(locale) {
sponsoring_contracts_download_open = false;
const orgs = current_organizations.filter((r) => r.is_selected === true);
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
for await (const o of orgs) {
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id
);
fetch(
`https://dev.lauf-fuer-kaya.de/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Sponsorings_" + o.name + ".pdf";
document.body.appendChild(a);
a.click();
a.remove();
if (count === orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
</script>
<ConfirmOrgDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open
bind:delete_org />
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
{#await promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('organizations-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_organizations.length === 0}
<OrgsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div class="h-12">
{#if current_organizations.some((r) => r.is_selected === true)}
<div id="sponsoring:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
sponsoring_contracts_download_open = !sponsoring_contracts_download_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-sponsoring-contracts')}
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="-mr-1 ml-2 h-5 w-5"><path fill="none" d="M0 0h24v24H0z"/><path fill="currentColor" d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"/></svg>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="sponsoring:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateSponsoringContract('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateSponsoringContract('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}
</div>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span
on:click={() => {
const newstate = !current_organizations.some((r) => r.is_selected === true);
current_organizations = current_organizations.map((r) => {
r.is_selected = newstate;
return r;
});
}}
class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)}
{$_('deselect-all')}
{:else}{$_('select-all')}{/if}
</span>
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('name')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('address')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('contact')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_organizations as o}
{#if Object.values(o)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr data-rowid="org_{o.id}">
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={o.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{o.name}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if o.address.address1 !== null}
{o.address.address1}<br />
{o.address.address2 || ''}<br />
{o.address.postalcode}
{o.address.city}
{o.address.country}
{/if}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if o.contact}
<a
href="../contacts/{o.contact.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname}
{o.contact.middlename || ''}
{o.contact.lastname}</a>
{:else}{$_('no-contact-specified')}{/if}
</div>
</div>
</div>
</td>
{#if active_deletes[o.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[o.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false)
.then((resp) => {
current_organizations = current_organizations.filter((obj) => obj.id !== o.id);
Toastify({
text: 'Organization deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_org = o;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{o.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')}
<button
on:click={() => {
active_deletes[o.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
let modal_open = false;
let delete_org = {};
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import store from "../../store";
import OrgsEmptyState from "./OrgsEmptyState.svelte";
import Toastify from "toastify-js";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
$: searchvalue = "";
$: active_deletes = [];
$: sponsoring_contracts_show = current_organizations.some((r) => r.is_selected === true);
$: cards_show = current_organizations.some((r) => r.is_selected === true);
$: generate_orgs = current_organizations.filter((r) => r.is_selected === true);
$: certificates_show = current_organizations.some(
(r) => r.is_selected === true
);
export let current_organizations = [];
const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
current_organizations = val;
}
);
</script>
<ConfirmOrgDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open
bind:delete_org />
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
{#await promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('organizations-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_organizations.length === 0}
<OrgsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="mb-4" />
<div class="h-12">
<GenerateSponsoringContracts
bind:sponsoring_contracts_show
bind:generate_orgs />
<GenerateRunnerCards
bind:cards_show
bind:generate_orgs />
<GenerateRunnerCertificates
bind:certificates_show
bind:generate_orgs />
</div>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span
on:click={() => {
const newstate = !current_organizations.some((r) => r.is_selected === true);
current_organizations = current_organizations.map((r) => {
r.is_selected = newstate;
return r;
});
}}
class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)}
{$_('deselect-all')}
{:else}{$_('select-all')}{/if}
</span>
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('name')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('address')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('contact')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_organizations as o}
{#if Object.values(o)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr data-rowid="org_{o.id}">
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={o.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{o.name}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if o.address.address1 !== null}
{o.address.address1}<br />
<!-- {o.address.address2 || ''}<br /> -->
{o.address.postalcode}
{o.address.city}
{o.address.country}
{/if}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if o.contact}
<a
href="../contacts/{o.contact.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname}
{o.contact.middlename || ''}
{o.contact.lastname}</a>
{:else}{$_('no-contact-specified')}{/if}
</div>
</div>
</div>
</td>
{#if active_deletes[o.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[o.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false)
.then((resp) => {
current_organizations = current_organizations.filter((obj) => obj.id !== o.id);
Toastify({
text: 'Organization deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_org = o;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{o.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')}
<button
on:click={() => {
active_deletes[o.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@@ -0,0 +1,418 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
RunnerCardService,
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { init } from "@paralleldrive/cuid2";
const createId = init({ length: 10, fingerprint: "lfk-frontend" });
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}-${createId()}.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}-${createId()}.pdf`;
} else {
a.download = `${$_("runnercards")}-${locale}-${createId()}.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}-${createId()}.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}-${createId()}.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}-${createId()}.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 z-10"
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,355 @@
<script>
import { _ } from "svelte-i18n";
import {
DonationService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { init } from "@paralleldrive/cuid2";
const createId = init({ length: 10, fingerprint: "lfk-frontend" });
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.filter((d) => d.runner?.id == runner.id) || [];
console.log(runner.distanceDonations);
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}-${createId()}.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.filter((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}-${createId()}.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.filter((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}-${locale}-${createId()}.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.filter((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}-${createId()}.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 z-10"
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

@@ -0,0 +1,324 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { init } from "@paralleldrive/cuid2";
const createId = init({ length: 10, fingerprint: "lfk-frontend" });
export let sponsoring_contracts_show = false;
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: sponsoring_contracts_download_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
function generateSponsoringContract(locale) {
sponsoring_contracts_download_open = false;
if (generate_orgs.length > 0) {
generateOrgContracts(locale);
} else if (generate_teams.length > 0) {
generateTeamContracts(locale);
} else {
generateRunnerContracts(locale);
}
}
async function generateTeamContracts(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
for (const t of generate_teams) {
count++;
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
fetch(
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("sponsorings")}_${t.name}-${locale}-${createId()}.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 generateOrgContracts(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
let count_orgs = 0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id,
true
);
await fetch(
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("sponsorings")}_${o.name}_direct-${locale}-${createId()}.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
);
await fetch(
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("sponsorings")}_${o.name}_${
t.name
}-${locale}-${createId()}.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) => {});
}
}
}
function generateRunnerContracts(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(generate_runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
if (generate_runners.length == 1) {
a.download = `${$_("sponsorings")}_${generate_runners[0].firstname}_${
generate_runners[0].lastname
}-${locale}-${createId()}.pdf`;
}
a.download = `${$_("sponsorings")}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
console.error(err);
});
}
</script>
{#if sponsoring_contracts_show}
<div id="sponsoring:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
sponsoring_contracts_download_open =
!sponsoring_contracts_download_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true"
>
{$_("generate-sponsoring-contracts")}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"
/></svg
>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10"
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

@@ -1,30 +0,0 @@
<script>
import { _, json } from "svelte-i18n";
import { getlang } from "./datatable_i18n";
import { Grid } from "gridjs";
//
let table;
const datatable = new Grid({
columns: ["Name", "Email", "Phone Number"],
language: getlang($json("datatable")),
sort: true,
search: { enabled: true },
data: [
["John", "john@example.com", "(353) 01 222 3333"],
["Mark", "mark@gmail.com", "(01) 22 888 4444"],
["Eoin", "eoin@gmail.com", "0097 22 654 00033"],
["Sarah", "sarahcdd@gmail.com", "+322 876 1233"],
["Afshin", "afshin@mail.com", "(353) 22 87 8356"],
],
pagination: {
enabled: true,
limit: 2,
summary: false,
},
});
setTimeout(() => {
datatable.render(table);
}, 0);
</script>
<div bind:this={table} />

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
RunnerService,
RunnerTeamService,
@@ -11,8 +11,10 @@
import isMobilePhone from "validator/es/lib/isMobilePhone";
import Toastify from "toastify-js";
import Select from "svelte-select";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let modal_open;
export let current_runners;
$: selected_team = undefined;
let firstname_input;
let lastname_input;
@@ -107,8 +109,7 @@
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_runners.push(result);
current_runners = current_runners;
dispatch("created", { runners: [result] });
})
.catch((err) => {
//
@@ -125,7 +126,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -0,0 +1,115 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_runner = {
id: 0,
firstname: "",
lastname: "",
};
const dispatch = createEventDispatcher();
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
async function submit() {
dispatch("delete", { id: delete_runner.id });
modal_open=false;
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
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
>
</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">
{$_("confirm-delete")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_("please-confirm-the-deletion-of-runner")}
</p>
</div>
<div class="w-full">
<span class="inline-block"
>{delete_runner.firstname} {delete_runner.lastname}</span
>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={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"
>
{$_("delete")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -3,7 +3,7 @@
import { read as readXlsx, utils as xlsx_utils } from "xlsx";
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import Toastify from "toastify-js";
import {
ImportService,
@@ -16,9 +16,13 @@
export let passed_org;
export let passed_orgs;
export let passed_team;
export let current_runners;
export let import_modal_open;
$: searchvalue = "";
$: importButtonEnabled =
recent_processed &&
(!(selected_org_or_team == "" || selected_org_or_team == null) ||
!(passed_org?.id == null || passed_org?.id == 0) ||
!(passed_team?.id == null || passed_team?.id == 0));
const dispatch = createEventDispatcher();
function cancelModal() {
json_output = [];
@@ -29,7 +33,7 @@
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
import_modal_open = false;
cancelModal();
}
if (e.keyCode === 13) {
//
@@ -44,7 +48,10 @@
groups = groups.concat(orgs);
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
const teams = val.map((r) => {
return { label: `${r.parentGroup.name} > ${r.name}`, value: `TEAM_${r.id}` };
return {
label: `${r.parentGroup.name} > ${r.name}`,
value: `TEAM_${r.id}`,
};
});
groups = groups.concat(teams);
});
@@ -120,6 +127,13 @@
.catch((err) => {
toast.hideToast();
recent_processed = true;
Toastify({
text: $_("error-during-import"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
cancelModal();
});
}
if (opened_from === "TeamDetail") {
@@ -137,6 +151,13 @@
.catch((err) => {
toast.hideToast();
recent_processed = true;
Toastify({
text: $_("error-during-import"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
cancelModal();
});
}
if (opened_from === "RunnerOverview") {
@@ -147,7 +168,7 @@
mapped
)
.then((resp) => {
current_runners = current_runners.concat(resp);
dispatch("created", { runners: resp });
toast.hideToast();
recent_processed = true;
Toastify({
@@ -160,6 +181,13 @@
.catch((err) => {
toast.hideToast();
recent_processed = true;
Toastify({
text: $_("error-during-import"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
cancelModal();
});
}
if (selected_org_or_team.includes("TEAM_")) {
@@ -169,11 +197,11 @@
mapped
)
.then((resp) => {
current_runners = current_runners.concat(resp);
dispatch("created", { runners: resp });
toast.hideToast();
recent_processed = true;
Toastify({
text: "Import finished",
text: $_('import-finished'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
@@ -182,6 +210,13 @@
.catch((err) => {
toast.hideToast();
recent_processed = true;
Toastify({
text: $_("error-during-import"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
cancelModal();
});
}
}
@@ -192,10 +227,10 @@
{#if import_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
import_modal_open = false;
cancelModal();
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
@@ -245,6 +280,16 @@
bind:files
type="file" />
</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 json_output.length > 0}
{#if opened_from === 'OrgOverview'}
@@ -349,6 +394,8 @@
</table>
</div>
<button
disabled={!importButtonEnabled}
class:opacity-50={!importButtonEnabled}
on:click={importAction}
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">

View File

@@ -1,398 +1,321 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import store from "../../store";
import {
RunnerService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail";
import Select from "svelte-select";
let data_loaded = false;
export let params;
const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid);
$: delete_triggered = false;
$: sponsoring_contracts_download_open = false;
$: original_data_pdf = {};
$: original_data = {};
$: editable = {};
$: group = {}
$: changes_performed = !(JSON.stringify(original_data) == JSON.stringify(editable));
$: isEmailValid =
(editable.email || "") === "" ||
(editable.email && isEmail(editable.email || ""));
$: isFirstnameValid = editable.firstname !== "";
$: isLastnameValid = editable.lastname !== "";
$: save_enabled =
changes_performed &&
isFirstnameValid &&
isLastnameValid &&
isEmailValid &&
editable.group != null;
runner_promise.then((data) => {
data_loaded = true;
original_data_pdf = Object.assign(original_data_pdf, data);
data.group = data.group.id;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
const orgs = val.map((r) => {
return { label: r.name, value: r };
});
groups = groups.concat(orgs);
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
const teams = val.map((r) => {
return { label: `${r.parentGroup.name} > ${r.name}`, value: r };
});
groups = groups.concat(teams);
group = groups.find(g => g.value.id == editable.group)
});
});
});
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
let groups = [];
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: $_("updating-runner"),
duration: 2500,
}).showToast();
let postdata = {};
postdata = Object.assign(postdata, editable);
RunnerService.runnerControllerPut(original_data.id, postdata)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
Toastify({
text: $_("runner-updated"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
} else {
}
}
function deleteRunner() {
RunnerService.runnerControllerRemove(original_data.id, true)
.then((resp) => {
location.replace("./");
})
.catch((err) => {});
}
function generateSponsoringContract(locale) {
sponsoring_contracts_download_open = false;
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`https://dev.lauf-fuer-kaya.de/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify([original_data_pdf]),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download =
"Sponsoring_" +
original_data.firstname +
(original_data.middlename || "") +
original_data.lastname +
".pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
console.error(err);
});
}
</script>
{#await runner_promise}
{$_('loading-runners')}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2"
fill="currentColor"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
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>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">{$_('runners')}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}
<span data-id="runner_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
{#if delete_triggered}
<button
on:click={deleteRunner}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
{/if}
<div id="sponsoring:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
sponsoring_contracts_download_open = !sponsoring_contracts_download_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-sponsoring-contract')}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="sponsoring:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateSponsoringContract('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateSponsoringContract('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-runner')}</button>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{/if}
</span>
</div>
<!-- -->
<div class="text-sm w-full">
<label
for="firstname"
class="font-medium text-gray-700">{$_('first-name')}</label>
<input
autocomplete="off"
placeholder={$_('first-name')}
type="text"
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label
for="middlename"
class="font-medium text-gray-700">{$_('middle-name')}</label>
<input
autocomplete="off"
placeholder={$_('middle-name')}
type="text"
bind:value={editable.middlename}
name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label
for="lastname"
class="font-medium text-gray-700">{$_('last-name')}</label>
<input
autocomplete="off"
placeholder={$_('last-name')}
type="text"
bind:value={editable.lastname}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label
for="email"
class="font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input
autocomplete="off"
placeholder={$_('e-mail-adress')}
type="email"
bind:value={editable.email}
class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid}
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-email-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label for="phone" class="font-medium text-gray-700">{$_('phone')}</label>
<input
autocomplete="off"
placeholder={$_('phone')}
type="tel"
bind:value={editable.phone}
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('group')}</span>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => label
.toLowerCase()
.includes(
filterText.toLowerCase()
) || option.id.value.toString().startsWith(filterText.toLowerCase())}
items={groups}
showChevron={true}
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')}
noOptionsMessage={$_('no-organization-or-team-found')}
bind:selectedValue={group}
on:select={(selectedValue) => {editable.group = selectedValue.detail.value.id}}
on:clear={() => (editable.group = null)} />
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('distance')}</span>
<br />
<span class="text-gray-700">{original_data.distance} km</span>
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import store from "../../store";
import {
RunnerService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail";
import Select from "svelte-select";
let data_loaded = false;
export let params;
const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid);
$: delete_triggered = false;
$: original_data_pdf = {};
$: original_data = {};
$: editable = {};
$: group = {};
$: changes_performed = !(
JSON.stringify(original_data) == JSON.stringify(editable)
);
$: isEmailValid =
(editable.email || "") === "" ||
(editable.email && isEmail(editable.email || ""));
$: isFirstnameValid = editable.firstname !== "";
$: isLastnameValid = editable.lastname !== "";
$: save_enabled =
changes_performed &&
isFirstnameValid &&
isLastnameValid &&
isEmailValid &&
editable.group != null;
$: sponsoring_contracts_show = true;
$: cards_show = true;
$: certificates_show = true;
$: generate_runners = [original_data_pdf];
runner_promise.then((data) => {
data_loaded = true;
original_data_pdf = Object.assign(original_data_pdf, data);
data.group = data.group.id;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
const orgs = val.map((r) => {
return { label: r.name, value: r };
});
groups = groups.concat(orgs);
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
const teams = val.map((r) => {
return { label: `${r.parentGroup.name} > ${r.name}`, value: r };
});
groups = groups.concat(teams);
group = groups.find((g) => g.value.id == editable.group);
});
}
);
});
let groups = [];
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: $_("updating-runner"),
duration: 2500,
}).showToast();
let postdata = {};
postdata = Object.assign(postdata, editable);
if (postdata.phone === "") {
postdata.phone = null;
}
RunnerService.runnerControllerPut(original_data.id, postdata)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
Toastify({
text: $_("runner-updated"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
} else {
}
}
function deleteRunner() {
RunnerService.runnerControllerRemove(original_data.id, true)
.then((resp) => {
location.replace("./");
})
.catch((err) => {});
}
</script>
{#await runner_promise}
{$_("loading-runners")}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2"
fill="currentColor"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
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
>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">{$_("runners")}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
</li>
<li class="flex items-center">
<span class="mr-2"
>{original_data.firstname}
{original_data.middlename || ""}
{original_data.lastname}</span
>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.firstname}
{original_data.middlename || ""}
{original_data.lastname}
<span data-id="runner_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:DELETE")}
{#if delete_triggered}
<button
on:click={deleteRunner}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm"
>{$_("cancel")}</button
>
{/if}
<GenerateSponsoringContracts
bind:sponsoring_contracts_show
bind:generate_runners
/>
<GenerateRunnerCards bind:cards_show bind:generate_runners />
<GenerateRunnerCertificates
bind:certificates_show
bind:generate_runners
/>
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("delete-runner")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("save-changes")}</button
>
{/if}
</span>
</div>
<!-- -->
<div class="text-sm w-full">
<label for="firstname" class="font-medium text-gray-700"
>{$_("first-name")}</label
>
<input
autocomplete="off"
placeholder={$_("first-name")}
type="text"
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("first-name-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label for="middlename" class="font-medium text-gray-700"
>{$_("middle-name")}</label
>
<input
autocomplete="off"
placeholder={$_("middle-name")}
type="text"
bind:value={editable.middlename}
name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div>
<div class="text-sm w-full">
<label for="lastname" class="font-medium text-gray-700"
>{$_("last-name")}</label
>
<input
autocomplete="off"
placeholder={$_("last-name")}
type="text"
bind:value={editable.lastname}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("last-name-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label for="email" class="font-medium text-gray-700"
>{$_("e-mail-adress")}</label
>
<input
autocomplete="off"
placeholder={$_("e-mail-adress")}
type="email"
bind:value={editable.email}
class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid}
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-email-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label for="phone" class="font-medium text-gray-700">{$_("phone")}</label>
<input
autocomplete="off"
placeholder={$_("phone")}
type="tel"
bind:value={editable.phone}
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_("group")}</span>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.id.value.toString().startsWith(filterText.toLowerCase())}
items={groups}
showChevron={true}
placeholder={$_("search-for-an-organization-or-team-by-name-or-id")}
noOptionsMessage={$_("no-organization-or-team-found")}
bind:selectedValue={group}
on:select={(selectedValue) => {
editable.group = selectedValue.detail.value.id;
}}
on:clear={() => (editable.group = null)}
/>
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_("distance")}</span>
<br />
<span class="text-gray-700">{original_data.distance / 1000} km</span>
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}

View File

@@ -7,35 +7,43 @@
$: current_runners = [];
export let modal_open = false;
export let import_modal_open = false;
let addRunners;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('runners')}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:CREATE')}
{$_("runners")}
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('laeufer-hinzufuegen')}
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"
>
{$_("laeufer-hinzufuegen")}
</button>
<button
on:click={() => {
import_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('import-runners')}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("import-runners")}
</button>
{/if}
</span>
<RunnersOverview bind:current_runners />
<RunnersOverview bind:current_runners bind:addRunners />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:CREATE')}
<AddRunnerModal bind:current_runners bind:modal_open />
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
<AddRunnerModal
bind:modal_open
on:created={(event) => {
addRunners(event.detail.runners);
}}
/>
<ImportRunnerModal
on:cancelDelete={(event) => {
import_modal_open = false;
@@ -43,7 +51,10 @@
passed_team={{}}
passed_orgs={[]}
passed_org={{}}
bind:current_runners
opened_from="RunnerOverview"
bind:import_modal_open />
bind:import_modal_open
on:created={(event) => {
addRunners(event.detail.runners);
}}
/>
{/if}

View File

@@ -1,364 +1,267 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
RunnerService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import store from "../../store";
import RunnersEmptyState from "./RunnersEmptyState.svelte";
import Select from "svelte-select";
import Toastify from "toastify-js";
$: searchvalue = "";
$: active_deletes = [];
export let current_runners = [];
const runners_promise = RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val;
});
$: selectedFilter_teams = null;
$: selectedFilter = null;
$: filter__teams = selectedFilter_teams || [];
$: filter__orgs = selectedFilter || [];
$: filterGroupIDs = filter__teams.concat(filter__orgs).map((i) => i.value);
$: sponsoring_contracts_download_open = false;
$: teams = [];
$: orgs = [];
$: mappedteams = teams.map(function (g) {
return { value: g.id, label: g.parentGroup.name + " > " + g.name };
});
$: selectgroups = orgs
.map(function (g) {
return { value: g.id, label: g.name };
})
.concat(mappedteams);
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
}
function generateSponsoringContract(locale) {
sponsoring_contracts_download_open = false;
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`https://dev.lauf-fuer-kaya.de/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(
current_runners.filter((r) => r.is_selected === true)
),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Sponsoring.pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
console.error(err);
});
}
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
{#await runners_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('runners-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_runners.length === 0}
<RunnersEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div class="block mb-6">
<label
for="country"
class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label>
<Select
on:select={(event) => {
selectedFilter = event.detail;
}}
selectedValue={selectedFilter}
placeholder={$_('filter-by-organization-team')}
containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
items={selectgroups}
isMulti={true} />
</div>
<div class="h-12">
{#if current_runners.some((r) => r.is_selected === true)}
<div id="sponsoring:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
sponsoring_contracts_download_open = !sponsoring_contracts_download_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-sponsoring-contracts')}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="sponsoring:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateSponsoringContract('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateSponsoringContract('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}
</div>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span
on:click={() => {
const newstate = !current_runners.some((r) => r.is_selected === true);
current_runners = current_runners.map((r) => {
r.is_selected = newstate;
return r;
});
}}
class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)}
{$_('deselect-all')}
{:else}{$_('select-all')}{/if}
</span>
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('name')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('contact-information')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('group')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('distance-in-km')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_runners as runner}
{#if runner.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || runner.middlename
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || runner.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(runner.id)}
{#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0}
<tr
data-rowid="user_{runner.id}"
data-groupid={runner.group.id}>
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={runner.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{runner.firstname}
{runner.middlename || ''}
{runner.lastname}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if runner.email}
<div class="text-sm text-gray-500">{runner.email}</div>
{/if}
{#if runner.phone}
<div class="text-sm text-gray-500">{runner.phone}</div>
{/if}
{#if runner.address.address1 !== null}
{runner.address.address1}<br />
{runner.address.address2 || ''}<br />
{runner.address.postalcode}
{runner.address.city}
{runner.address.country}
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if runner.group.responseType === 'RUNNERTEAM'}
<a
href="../teams/{runner.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
{/if}
{#if runner.group.responseType === 'RUNNERORGANIZATION'}
<a
href="../orgs/{runner.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{runner.distance}
</td>
{#if active_deletes[runner.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[runner.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
RunnerService.runnerControllerRemove(runner.id, true)
.then((resp) => {
current_runners = current_runners.filter((obj) => obj.id !== runner.id);
})
.catch((err) => {});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{runner.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
<button
on:click={() => {
active_deletes[runner.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}
<script>
import {
RunnerOrganizationService,
RunnerService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
import { writable } from "svelte/store";
import store from "../../store";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import InputElement from "../shared/InputElement.svelte";
import TableActions from "../shared/TableActions.svelte";
import { groupFilter } from "../shared/tablefilters";
import DeleteRunnerModal from "./DeleteRunnerModal.svelte";
import Toastify from "toastify-js";
import TableBottom from "../shared/TableBottom.svelte";
import TableHeader from "../shared/TableHeader.svelte";
$: selectedRunners =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: active_delete = undefined;
let dataLoaded = false;
export let current_runners = [];
$: sponsoring_contracts_show = selected.length > 0;
$: cards_show = selected.length > 0;
$: certificates_show = selected.length > 0;
$: teams = [];
$: orgs = [];
export const addRunners = (runners) => {
current_runners = current_runners.concat(...runners);
options.update((options) => ({
...options,
data: current_runners,
}));
};
//Section table
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "firstname",
header: () => $_("first-name"),
filterFn: `includesString`,
},
{
accessorKey: "middlename",
header: () => $_("middle-name"),
cell: (info) => {
if (!info || !info.getValue()) {
return "";
}
return info.getValue();
},
filterFn: `includesString`,
},
{
accessorKey: "lastname",
header: () => $_("last-name"),
filterFn: `includesString`,
},
{
accessorKey: "group",
header: () => $_("group"),
cell: (info) => {
const group = info.getValue();
if (group.responseType === "RUNNERORGANIZATION") {
return group.name;
}
return `${group.parentGroup.name} > ${group.name}`;
},
filterFn: `group`,
},
{
accessorKey: "distance",
header: () => $_("distance"),
cell: (info) => {
if (info.getValue() < 1000) {
return `${info.getValue()} m`;
}
return `${(info.getValue() / 1000).toFixed(1)} km`;
},
enableColumnFilter: false,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_delete =
current_runners[
current_runners.findIndex((r) => r.id == info.row.original.id)
];
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes(
"RUNNER:DELETE"
),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
filterFns: {
group: groupFilter,
},
initialState: {
pagination: {
pageSize: 50,
},
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
async function deleteRunner(delete_runner_id) {
await RunnerService.runnerControllerRemove(delete_runner_id, true);
current_runners = current_runners.filter((r) => r.id !== delete_runner_id);
options.update((options) => ({
...options,
data: current_runners,
}));
Toastify({
text: $_("runner-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
onMount(async () => {
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
orgs = val;
}
);
let page = 0;
while (page >= 0) {
const runners = await RunnerService.runnerControllerGetAll(page, 1000);
if (runners.length == 0) {
page = -2;
}
current_runners = current_runners.concat(...runners);
options.update((options) => ({
...options,
data: current_runners,
}));
dataLoaded = true;
page++;
}
console.log("All runners loaded");
});
</script>
<DeleteRunnerModal
delete_runner={active_delete}
modal_open={active_delete != undefined}
on:delete={(event) => {
deleteRunner(event.detail.id);
}}
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")}
{#if !dataLoaded}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("runners-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:else}
<div class="h-12 mt-2">
<GenerateSponsoringContracts
bind:sponsoring_contracts_show
bind:generate_runners={selectedRunners}
/>
<GenerateRunnerCards
bind:cards_show
bind:generate_runners={selectedRunners}
/>
<GenerateRunnerCertificates
bind:certificates_show
bind:generate_runners={selectedRunners}
/>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
{#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<InputElement
type="checkbox"
checked={$table.getIsAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()}
/>
</th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr>
{/each}
</thead>
<tbody>
{#each $table.getRowModel().rows as row}
<tr>
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement
type="checkbox"
checked={row.getIsSelected()}
on:change={() => row.toggleSelected()}
/>
</td>
{#each row.getVisibleCells() as cell}
<td>
<svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
<div class="h-2" />
{/if}
{/if}
<TableBottom {table} {selected} />

View File

@@ -0,0 +1,35 @@
<script>
import { _ } from "svelte-i18n";
export let groups;
export let handler;
let selected = "all";
</script>
<th style="border-bottom: 1px solid #ddd;">
<select
on:input={() => {
setTimeout(() => {
if (`${selected}`.trim()) {
const value = selected;
handler.filter(value, (runner) => {
if (
runner.group.id === value ||
runner?.group?.parentGroup?.id === value ||
value === "all"
)
return runner;
return "";
});
}
}, 50);
}}
bind:value={selected}
name="groupfilter"
id="groupfilter"
>
<option value="all">{$_('all')}</option>
{#each groups as g}
<option value={g.value}>{g.label}</option>
{/each}
</select>
</th>

View File

@@ -1,23 +1,24 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
RunnerService,
ScanService,
} from "@odit/lfk-client-js";
import Select from "svelte-select";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let modal_open;
export let current_scans;
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase());
option.value.id.toString().startsWith(filterText.toLowerCase());
function focus(el) {
el.focus();
}
const dispatch = createEventDispatcher();
$: runner = 0;
$: runners = [];
RunnerService.runnerControllerGetAll().then((val) => {
@@ -63,8 +64,7 @@
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_scans.push(result);
current_scans = current_scans;
dispatch("created", { scans: [result] });
})
.catch((err) => {
//
@@ -81,7 +81,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -0,0 +1,110 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_scan = {
id: 0,
runner: {
firstname: "",
lastname: "",
},
};
const dispatch = createEventDispatcher();
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
async function submit() {
dispatch("delete", { id: delete_scan.id });
modal_open = false;
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
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
>
</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">
{$_("confirm-delete")}
</h3>
<div class="mt-2 mb-6">
{$_("please-confirm-the-deletion-of-scan")} #{delete_scan.id}
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={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"
>
{$_("delete")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,16 @@
<script>
import { _ } from "svelte-i18n";
export let valid = false;
</script>
{#if valid}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("valid")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("invalid")}</span
>
{/if}

View File

@@ -5,6 +5,7 @@
import ScansOverview from "./ScansOverview.svelte";
$: current_scans = [];
export let modal_open = false;
let addScans;
</script>
<section class="container p-5">
@@ -21,9 +22,11 @@
</button>
{/if}
</span>
<ScansOverview bind:current_scans />
<ScansOverview bind:current_scans bind:addScans />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:CREATE')}
<AddScanModal bind:current_scans bind:modal_open />
<AddScanModal bind:modal_open on:created={(event)=>{
addScans(event.detail.scans)
}} />
{/if}

View File

@@ -5,7 +5,7 @@
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full" style="height:15rem" src={scans_empty} alt="" />
<img class="m-auto" style="height:15rem" src={scans_empty} alt="" />
<span class="font-bold">{$_('there-are-no-scans-yet')}</span><br />
<span>{$_('add-your-fist-scan')}</span>
</p>

View File

@@ -1,201 +1,308 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
ScanService,
} from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import { ScanService, TrackService } from "@odit/lfk-client-js";
import store from "../../store";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { onMount } from "svelte";
import { writable } from "svelte/store";
import Toastify from "toastify-js";
import TableBottom from "../shared/TableBottom.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import ScansEmptyState from "./ScansEmptyState.svelte";
$: searchvalue = "";
$: active_deletes = [];
import InputElement from "../shared/InputElement.svelte";
import TableActions from "../shared/TableActions.svelte";
import { runnerFilter, statusFilter } from "../shared/tablefilters";
import CardRunner from "../cards/CardRunner.svelte";
import ScanValid from "./ScanValid.svelte";
import DeleteScanModal from "./DeleteScanModal.svelte";
$: selectedScans =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: active_delete = undefined;
$: dataLoaded = false;
export let current_scans = [];
const scans_promise = ScanService.scanControllerGetAll().then((val) => {
current_scans = val;
export const addScans = (scans) => {
current_scans = current_scans.concat(...scans);
options.update((options) => ({
...options,
data: current_scans,
}));
};
let allTracks = [];
TrackService.trackControllerGetAll().then((val) => {
allTracks = val;
});
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
function format_laptime(laptime) {
if (laptime == 0 || laptime == null) {
return $_("first-scan-of-the-day");
}
return id.toString() === searchvalue;
if (laptime < 60) {
return `${laptime}s`;
}
if (laptime < 3600) {
return `${Math.floor(laptime / 60)}min ${
laptime - Math.floor(laptime / 60) * 60
}s`;
}
return `${Math.floor(laptime / 3600)}h ${
laptime - Math.floor(laptime / 3600) * 3600
}min ${
laptime -
Math.floor(laptime / 3600) * 3600 -
Math.floor(laptime / 60) * 60
}`;
}
function format_laptime(laptime){
if(laptime == 0 || laptime == null){return $_('first-scan-of-the-day')}
if(laptime < 60){return `${laptime}s`}
if(laptime < 3600){return `${Math.floor(laptime / 60)}min ${laptime - (Math.floor(laptime / 60)*60)}s`}
return `${Math.floor(laptime / 3600)}h ${laptime - (Math.floor(laptime / 3600)*3600)}min ${laptime - (Math.floor(laptime / 3600)*3600) - (Math.floor(laptime / 60)*60)}`
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "runner",
header: () => $_("runner"),
cell: (info) => {
return renderComponent(CardRunner, { runner: info.getValue() });
},
filterFn: `runner`,
},
{
accessorKey: "lapTime",
header: () => $_("laptime"),
cell: (info) => {
return format_laptime(info.getValue());
},
enableColumnFilter: false,
},
{
accessorKey: "timestamp",
header: () => $_("timestamp"),
cell: (info) => {
return new Date(parseInt(info.getValue()) * 1000).toLocaleString();
},
enableColumnFilter: false,
},
{
accessorKey: "distance",
header: () => $_("distance"),
cell: (info) => {
if (info.getValue() < 1000) {
return `${info.getValue()}m`;
}
return `${(info.getValue() / 1000).toFixed(1)}km`;
},
enableColumnFilter: false,
},
{
accessorKey: "track",
header: () => $_("track"),
cell: (info) => {
const track = info.getValue();
return track?.name || "?";
},
enableColumnFilter: true,
},
{
accessorKey: "valid",
cell: (info) => {
return renderComponent(ScanValid, { valid: info.getValue() });
},
header: () => $_("status"),
filterFn: `status`,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_delete =
current_scans[
current_scans.findIndex((r) => r.id == info.row.original.id)
];
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes("SCAN:DELETE"),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
filterFns: {
runner: runnerFilter,
status: statusFilter,
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
async function deleteScan(scan_id) {
await ScanService.scanControllerRemove(scan_id, true);
current_scans = current_scans.filter((r) => r.id !== scan_id);
options.update((options) => ({
...options,
data: current_scans,
}));
Toastify({
text: $_("scan-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
onMount(async () => {
let page = 0;
while (page >= 0) {
const scans = await ScanService.scanControllerGetAll(page, 500);
if (scans.length == 0) {
page = -2;
}
current_scans = current_scans.concat(...scans);
options.update((options) => ({
...options,
data: current_scans,
}));
dataLoaded = true;
page++;
}
console.log("All scans loaded");
});
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')}
{#await scans_promise}
<DeleteScanModal
delete_scan={active_delete}
modal_open={active_delete != undefined}
on:delete={(event) => {
deleteScan(event.detail.id);
}}
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")}
{#if !dataLoaded}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('scans-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
role="alert"
>
<p class="font-bold">{$_("scans-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then}
{#if current_scans.length === 0}
<ScansEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('runner')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('distance-track')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('laptime')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('status')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_scans as scan}
{#if scan.track?.name
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || scan.runner?.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || scan.runner?.middlename
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || scan.runner?.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(scan.id)}
<tr data-rowid="scan_{scan.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<a
href="../runners/{scan.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.runner.firstname}
{scan.runner.middlename || ''}
{scan.runner.lastname}</a>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
{#if scan.distance < 1000}
{scan.distance}m
{:else}{scan.distance / 1000}km{/if}
{#if scan.track}
<a
href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.track.name}
</a>
{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if scan.responseType === "TRACKSCAN"}
<div class="text-sm font-medium text-gray-900">
{format_laptime(scan.lapTime)}
</div>
{:else}
<div class="text-sm font-medium text-gray-900">
{$_('scan-with-fixed-distance')}
</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if scan.valid}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('valid')}</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('invalid')}</span>
{/if}
</div>
</td>
{#if active_deletes[scan.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[scan.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
ScanService.scanControllerRemove(scan.id, false).then(
(resp) => {
current_scans = current_scans.filter(
(obj) => obj.id !== scan.id
);
Toastify({
text: 'Scan deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{scan.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:DELETE')}
<button
on:click={() => {
active_deletes[scan.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{:else if current_scans.length === 0}
<ScansEmptyState />
{:else}
{#if selected.length > 0}
<button
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 inline-flex"
id="options-menu"
on:click={async () => {
const prom = [];
for (const scan of selectedScans) {
prom.push(ScanService.scanControllerRemove(scan.id, true));
}
await Promise.all(prom);
for (const scan of selectedScans) {
current_scans = current_scans.filter((r) => r.id !== scan.id);
}
options.update((options) => ({
...options,
data: current_scans,
}));
$table.resetRowSelection();
Toastify({
text: $_("scan-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}}
>
{$_("delete-scans")}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
{/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 class="overflow-x-auto">
<table class="w-full">
<thead>
{#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<InputElement
type="checkbox"
checked={$table.getIsAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()}
/>
</th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr>
{/each}
</thead>
<tbody>
{#each $table.getRowModel().rows as row}
<tr>
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement
type="checkbox"
checked={row.getIsSelected()}
on:change={() => row.toggleSelected()}
/>
</td>
{#each row.getVisibleCells() as cell}
<td>
<svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
{/await}
<TableBottom {table} {selected} />
{/if}
{/if}

View File

@@ -0,0 +1,50 @@
<script>
export let handler;
let filterValue = "";
</script>
<th>
<input
on:input={() => {
setTimeout(() => {
const v = filterValue.toLowerCase();
handler.filter(v, (c) => {
if (v.startsWith("#")) {
return `#${c.runner?.id}`;
}
if (c.runner) {
let runnerName = `${c.runner.firstname} ${c.runner.lastname}`;
if (c.runner.middlename) {
runnerName = `${c.runner.firstname} ${c.runner.middlename} ${c.runner.lastname}`;
}
runnerName = runnerName.toLowerCase();
return runnerName;
}
return "";
});
}, 150);
}}
placeholder="Filter"
bind:value={filterValue}
type="text"
name="runnerfilter"
id="runnerfilter"
/>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
input {
margin: -1px 0 0 0;
padding: 0;
width: 100%;
height: 24px;
border: none;
text-align: left;
background: inherit;
outline: 0;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,31 @@
<script>
import { _ } from "svelte-i18n";
export let tracks;
export let handler;
let selected = "all";
</script>
<th style="border-bottom: 1px solid #ddd;">
<select
on:input={() => {
setTimeout(() => {
if (`${selected}`.trim()) {
const value = selected;
handler.filter(value, (scan) => {
// TODO: fix filter
if (scan.track.id === value || value === "all") return scan.track.id;
return "";
});
}
}, 50);
}}
bind:value={selected}
name="trackfilter"
id="trackfilter"
>
<option value="all">{$_("all")}</option>
{#each tracks as track}
<option value={track.id}>{track.name}</option>
{/each}
</select>
</th>

View File

@@ -1 +1 @@
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="598.11" height="535.11"><path d="M3.35 120.07a4.6 4.6 0 00-3.18 5.66l76.72 273.98a4.6 4.6 0 005.65 3.18l282.82-79.19a4.6 4.6 0 003.18-5.66L291.82 44.07a4.6 4.6 0 00-5.65-3.19z" fill="#e6e6e6"/><path d="M86.1 389.95l269.5-75.46-72.99-260.67-269.5 75.46z" fill="#fff"/><path d="M48.74 164.1c-1.8.5-2.54 3.48-1.65 6.65s3.07 5.33 4.87 4.83l122.91-34.42c1.8-.5 2.54-3.49 1.66-6.65s-3.08-5.34-4.87-4.84zM58.64 199.44c-1.8.5-2.54 3.5-1.65 6.66s3.07 5.34 4.87 4.83l122.91-34.42c1.8-.5 2.54-3.49 1.65-6.65s-3.07-5.34-4.86-4.83zM68.42 234.39c-1.8.5-2.54 3.49-1.65 6.66s3.07 5.33 4.87 4.83l122.92-34.42c1.8-.5 2.54-3.5 1.65-6.66s-3.07-5.33-4.87-4.83zM78.32 269.74c-1.8.5-2.54 3.49-1.65 6.66s3.07 5.33 4.87 4.83l122.92-34.42c1.8-.5 2.54-3.49 1.65-6.66s-3.07-5.33-4.87-4.83zM234.04 112.61a5.97 5.97 0 103.21 11.5l22.98-6.44a5.97 5.97 0 00-3.22-11.49zM243.74 147.28a5.97 5.97 0 103.22 11.49l22.98-6.43a5.97 5.97 0 00-3.22-11.5zM253.45 181.95a5.97 5.97 0 103.22 11.49l22.98-6.44a5.97 5.97 0 00-3.22-11.49zM263.16 216.61a5.97 5.97 0 003.21 11.5l22.98-6.44a5.97 5.97 0 00-3.21-11.49z" fill="#e6e6e6"/><path d="M272.43 276.7a7.6 7.6 0 104.1 14.64l29.28-8.2a7.6 7.6 0 00-4.1-14.64z" fill="#6c63ff"/><path fill="#e6e6e6" d="M85.9 307.81l216.66-60.67.54 1.93-216.67 60.67z"/><path fill="#a0616a" d="M520.2 506.07l-17.38 4.2-24.47-65.02 25.65-6.2 16.2 67.02z"/><path d="M472.85 535.11l-.12-.48a22.23 22.23 0 0116.37-26.8l34-8.23 5.33 22.08z" fill="#2f2e41"/><path fill="#a0616a" d="M443.28 517.91H425.4l-8.5-68.96h26.38v68.96z"/><path d="M447.6 535.01h-57.18v-.5a22.2 22.2 0 0122.2-22.2h34.99zM416.88 490.99l-17.36-206.87 71.86-13.25.28-.05 21.03 13.52-7.32 76.14 33.7 118.7-29.1 7.65-33.75-110.08-7.73-33.48-3.96 43.5 2.94 107.28z" fill="#2f2e41"/><path d="M397.3 288.81l-.2-.24 24.84-186.96.03-.24.17-.18c.37-.36 9.07-8.96 18.02-8.96 1.3 0 2.52-.03 3.7-.06 6.85-.18 12.26-.32 18.69 6.1 6.55 6.56 27.92 30.47 27.92 63.23 0 31.7 2.88 130.22 2.91 131.21l.04 1.4-1.16-.76c-.3-.19-29.03-18.49-53.14-1.48-7.53 5.32-14.3 7.18-20.09 7.18-13.47 0-21.62-10.1-21.73-10.24z" fill="#6c63ff"/><circle cx="737.3" cy="227.82" r="35.82" transform="rotate(-28.66 229.78 725.57)" fill="#a0616a"/><path d="M381.53 328.99a14.66 14.66 0 00.85-22.47l20.34-47.97-26.63 4.9-15.23 44.8A14.74 14.74 0 00381.53 329z" fill="#a0616a"/><path d="M361.88 291.67l6.55-13.83a2.7 2.7 0 01-.97-1c-6.12-10.6 30.84-98.67 33.3-104.51-.37-3.18-4.25-36.85-1.41-48.2 3.34-13.35 10.2-19.58 22.93-20.81 14.04-1.32 17.83 17.75 17.86 17.94l.02 49.02-16.12 56.43-36.75 74.97z" fill="#6c63ff"/><path d="M440.94 58.87c-4.3.56-7.54-3.83-9.04-7.9s-2.64-8.78-6.38-10.98c-5.1-3-11.62.61-17.45-.38-6.59-1.11-10.87-8.1-11.2-14.76s2.31-13.1 4.92-19.24l.9 7.64A15.16 15.16 0 01409.33 0l-1.17 11.22c.73-6.29 7.5-11.16 13.7-9.85l-.19 6.68c7.6-.9 15.28-1.81 22.91-1.12s15.31 3.1 21.1 8.13c8.64 7.51 11.8 19.89 10.74 31.3s-5.77 22.13-10.68 32.48c-1.23 2.6-2.94 5.54-5.8 5.88-2.58.3-4.93-1.86-5.73-4.32s-.41-5.14.07-7.69c.72-3.84 1.63-7.77.95-11.63s-3.45-7.66-7.33-8.13-7.86 3.97-6 7.4z" fill="#2f2e41"/><path fill="#3f3d56" d="M597.73 535.1H339.99v-2.11h258.12l-.38 2.1z"/></svg>
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 647.6 632.2"><path d="M411 142H237a15 15 0 00-15 15v388l-2 1-43 13a8 8 0 01-10-6L39 137a8 8 0 016-10l66-20 191-58 66-20a8 8 0 0110 5l33 106z" fill="#f2f2f2"/><path d="M449 140L410 12a17 17 0 00-21-11l-93 28-191 59-93 28a17 17 0 00-11 21l134 438a17 17 0 0016 12 17 17 0 005 0l64-20 2-1v-2l-2 1-65 20a15 15 0 01-18-10L3 137a15 15 0 0110-19l92-28 192-59 92-28a15 15 0 015-1 15 15 0 0114 11l39 127 1 2h2z" fill="#3f3d56"/><path d="M123 128a9 9 0 01-9-7l-13-42a9 9 0 016-11l176-54a9 9 0 0111 6l13 42a9 9 0 01-6 12l-176 53a9 9 0 01-2 1z" fill="#6c63ff"/><circle cx="190.2" cy="25" r="20" fill="#6c63ff"/><circle cx="190.2" cy="25" r="12.7" fill="#fff"/><path d="M603 582H265a9 9 0 01-9-8V169a9 9 0 019-9h338a9 9 0 018 9v405a9 9 0 01-8 8z" fill="#e6e6e6"/><path d="M447 140H237a17 17 0 00-17 17v408l2-1V157a15 15 0 0115-15h211zm184 0H237a17 17 0 00-17 17v458a17 17 0 0017 17h394a17 17 0 0017-17V157a17 17 0 00-17-17zm15 475a15 15 0 01-15 15H237a15 15 0 01-15-15V157a15 15 0 0115-15h394a15 15 0 0115 15z" fill="#3f3d56"/><path d="M526 184H342a9 9 0 01-9-9v-44a9 9 0 019-9h184a9 9 0 019 9v44a9 9 0 01-9 9z" fill="#6c63ff"/><circle cx="433.6" cy="105.2" r="20" fill="#6c63ff"/><circle cx="433.6" cy="105.2" r="12.2" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { ScanStationService, TrackService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import Select from "svelte-select";
@@ -81,7 +81,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { ScanStationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
@@ -13,7 +13,7 @@
dispatch("cancelDelete", { id: delete_station.id });
}
function deleteStation() {
ScanStationService.donorControllerRemove(
ScanStationService.scanStationControllerRemove(
delete_station.id,
true
)
@@ -32,7 +32,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div

View File

@@ -1,14 +1,18 @@
<script>
import { _ } from "svelte-i18n";
import { focusTrap } from "svelte-focus-trap";
import Toastify from "toastify-js";
import { tick, createEventDispatcher } from "svelte";
import bwipjs from "bwip-js";
export let copy_modal_open;
export let new_station;
const dispatch = createEventDispatcher();
let valueCopy = null;
let areaDom;
let copied = false;
$: is_qrcode = false;
$: barcode = textToBase64Barcode(new_station.key, is_qrcode);
function close() {
copy_modal_open = false;
}
@@ -23,100 +27,180 @@
throw new Error();
}
Toastify({
text: $_('copied-token-to-clipboard'),
text: $_("copied-token-to-clipboard"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
copied = true;
} catch (err) {
Toastify({
text: $_('error-whyile-copying-to-clipboard'),
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;
}
function textToBase64Barcode(text, is_qrcode) {
const canvas = document.createElement("canvas");
let bcid = "code128";
if (is_qrcode) {
bcid = "qrcode";
}
let codeconfig = {
bcid,
text: `${text}`,
scale: 4,
includetext: true,
textxalign: "center",
backgroundcolor: "ffffff",
};
if (bcid == "code128") {
codeconfig.height = 10;
}
bwipjs.toCanvas(canvas, codeconfig);
return canvas.toDataURL("image/png");
}
</script>
{#if copy_modal_open}
{#if valueCopy != null}
<textarea bind:this={areaDom}>{valueCopy}</textarea>
{/if}
<div class="fixed z-10 inset-0 overflow-y-auto" use:focusTrap>
<div class="fixed z-10 inset-0 overflow-y-auto">
<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="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
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">
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">{$_('token')}</h3>
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("token")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again')}
{$_(
"the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again"
)}
<br />
{$_('please-copy-the-token-and-store-it-somewhere-save')}
{$_("please-copy-the-token-and-store-it-somewhere-save")}
</p>
</div>
<div class="mt-2 mb-6">
<label
for="token"
class="block text-sm font-medium text-gray-700">Token</label>
<div on:click={copy} class="inline-flex">
class="block text-sm font-medium text-gray-700"
>{$_("token")}</label
>
<button on:click={copy} class="inline-flex">
<p
name="token"
class:bg-green-200={copied}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2">
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
>
{new_station.key}
</p>
<div
class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer">
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" />
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>
d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z"
/></svg
>
</div>
</div>
<p class="text-gray-500 text-xs">{$_('click-to-copy-token-to-clipboard')}</p>
</button>
<p class="text-gray-500 text-xs">
{$_("click-to-copy-token-to-clipboard")}
</p>
</div>
</div>
</div>
<div class="mx-auto text-center items-center">
<h2 class="text-lg leading-6 font-medium text-gray-900">
{$_("config-codes")}
</h2>
<span class="flex items-center text-center">
<p class="text-md text-gray-900 mr-3">Format:</p>
<label for="codeswitch" class="text-md text-gray-900 mr-3"
>Code128</label
>
<input
id="codeswitch"
type="checkbox"
bind:checked={is_qrcode}
class="relative shrink-0 w-[3.25rem] h-7 bg-gray-100 checked:bg-none checked:bg-blue-600 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 border border-transparent ring-1 ring-transparent focus:border-blue-600 focus:ring-blue-600 ring-offset-white focus:outline-none appearance-none before:inline-block before:w-6 before:h-6 before:bg-white checked:before:bg-blue-200 before:translate-x-0 checked:before:translate-x-full before:shadow before:rounded-full before:transform before:ring-0 before:transition before:ease-in-out before:duration-200 dark:before:bg-gray-400 dark:checked:before:bg-blue-200"
/>
<label for="codeswitch" class="text-md text-gray-900 ml-3"
>QR-Code</label
>
</span>
<h3 class="leading-6 font-medium text-gray-900">
{$_("api-endpoint")}
</h3>
<img
class:w-[50%]={is_qrcode}
class:w-full={!is_qrcode}
class="md:w-auto mb-2 mx-auto"
alt="Registrierungscode"
src={textToBase64Barcode(config.baseurl, is_qrcode)}
/>
<h3 class="leading-6 font-medium text-gray-900">{$_("token")}</h3>
<img
class:w-[50%]={is_qrcode}
class:w-full={!is_qrcode}
class="md:w-auto mb-2 mx-auto"
alt="Registrierungscode"
src={barcode}
/>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={close}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('yes-i-copied-the-token')}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("yes-i-copied-the-token")}
</button>
</div>
</div>

View File

@@ -41,7 +41,7 @@
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
class="mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { MeService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
@@ -17,7 +17,7 @@
MeService.meControllerRemove(true)
.then((resp) => {
Toastify({
text: "Profile deleted!",
text: $_('profile-deleted'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
@@ -30,7 +30,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div

Some files were not shown because too many files have changed in this diff Show More