Compare commits

..

115 Commits
1.3.0 ... 1.5.2

Author SHA1 Message Date
3532968b33 🚀RELEASE v1.5.2 2024-11-21 10:33:56 +01:00
6bb49db4ee feat(dashboard): add lfk icon and app name to mobile nav bar 2024-11-21 10:24:23 +01:00
38fb111f7a feat: improved mobile buttons + search ui 2024-11-21 10:22:06 +01:00
a50447f457 feat(dashboard): improved a11y of active sidebar menu item 2024-11-21 10:14:22 +01:00
b1a2044631 feat(dashboard): match greeting style with rest of titles 2024-11-21 10:11:03 +01:00
c883920caa feat: improved dashboard titles ui + a11y 2024-11-21 10:09:14 +01:00
21453ef272 feat: improved dashboard titles ui + a11y 2024-11-21 10:07:20 +01:00
10182433f8 feat(i18n/de): rename "Track" to "Laufstrecke" 2024-11-21 10:04:38 +01:00
b338f33a63 feat(dashboard): improved mobile ui hamburger button 2024-11-21 09:58:15 +01:00
cb82200481 feat(users/UsersOverview): improve ui by adding borders to badges 2024-11-21 09:57:50 +01:00
9abf74d6d2 🚀RELEASE v1.5.1 2024-11-21 09:46:27 +01:00
50e81a6cb5 fix(dockerfile): AS casing 2024-11-21 09:45:51 +01:00
8fae1fb6b3 chore(deps): bump some 2024-11-21 09:45:39 +01:00
372fa110ec fix(scanstations): CopyScanStationTokenModal open after create 2024-11-21 09:40:32 +01:00
35bec9fe58 chore(deps): pnpm@9 2024-11-21 09:33:22 +01:00
93d67bdba9 chore(deps): node:23.2.0 2024-11-21 09:32:58 +01:00
a5e72a18e3 refactor(scanstations/CopyScanStationTokenModal): drop dispatch 2024-11-21 09:31:45 +01:00
91d2f46b93 fix(config): add explicit window.config 2024-11-21 09:31:09 +01:00
c60bae4533 fix(tracks/AddTrackModal): i18n 2024-11-21 09:30:52 +01:00
43ac878d44 fix: tailwind config 2024-11-21 09:30:24 +01:00
ceabd06a43 🚀RELEASE v1.5.0 2024-11-20 19:29:18 +01:00
9bfc0c5338 fix(components): Add missing toast imports 2024-11-20 19:28:07 +01:00
fb8206ff13 feat(ci)!: Switch to woodpecker
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2023-11-06 19:51:01 +01:00
dceb0ef461 🚀RELEASE v1.4.13
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-07-31 18:55:53 +02:00
88bc1982ca Show donations as euro in export 2023-07-31 18:55:09 +02:00
e741a9d7e7 Merge branch 'dev' of git.odit.services:lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-18 21:35:28 +02:00
65f1d22205 🚀RELEASE v1.4.12 2023-05-18 21:35:16 +02:00
d867c08aba fix(donation/payment): Funny javascript number to float conversion where integers were needed 2023-05-18 21:34:41 +02:00
6193eff38e new license file version [CI SKIP] 2023-05-10 11:29:03 +00:00
f1929e7cf9 Merge branch 'dev' of git.odit.services:lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-10 13:28:43 +02:00
373484c242 🚀RELEASE v1.4.11 2023-05-10 13:28:34 +02:00
f77460bb0c chore(deps): Lockfile 2023-05-10 13:28:21 +02:00
574e0dcb05 feat(orgs): Show total distance 2023-05-10 13:27:47 +02:00
7b19a0aa08 chore(deps): More bumps 2023-05-10 13:22:11 +02:00
08642d7618 new license file version [CI SKIP] 2023-05-10 11:21:43 +00:00
c3e9c27cd3 🚀RELEASE v1.4.10
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-10 13:21:10 +02:00
29a2854671 chore(deps): Bumped svelte-table 2023-05-10 13:20:44 +02:00
8e6786e722 chore(deps): Pin and bump 2023-05-10 13:19:25 +02:00
6ad40564e3 chore(deps): Bumped scanclientjs 2023-05-10 13:18:44 +02:00
776973bfe9 🚀RELEASE v1.4.9
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-09 16:32:00 +02:00
6025e43baa Fixed empty return 2023-05-09 16:29:41 +02:00
d9a47f882c Changed the in table replacement method 2023-05-09 16:29:25 +02:00
4235758a6d 🚀RELEASE v1.4.8
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-09 10:44:04 +02:00
59fe2dfabb Switched donor loading to non-paginated 2023-05-09 10:43:33 +02:00
6364536dcd 🚀RELEASE v1.4.7
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-04 20:30:20 +02:00
a8a771114d Paginated modal data loading 2023-05-04 20:28:21 +02:00
4e0a2c8301 Moved loading to onmount 2023-05-04 20:17:29 +02:00
b6fed92a17 🚀RELEASE v1.4.6
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-04 20:10:57 +02:00
97b57aeb0c fix(donor/detail): Set email to null to avoid vaidation errors 2023-05-04 20:10:21 +02:00
e25ed1fff9 fix(donor/detail): Set phone to null to avoid vaidation errors 2023-05-04 20:09:43 +02:00
a2ff5b8a14 fix(donor/details): don't load donations 2023-05-04 20:07:54 +02:00
0284f18beb 🚀RELEASE v1.4.5
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-04 20:04:02 +02:00
803d64c78c fix: Removed dynamic pagesize adjustments
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-04 20:03:23 +02:00
dacb2f8ace Revert "revert: buggy pagination"
This reverts commit b2648645e8.
2023-05-04 19:57:46 +02:00
b7a53960e5 🚀RELEASE v1.4.4
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-04 14:44:31 +02:00
66f1e6b4fe fix(AddDonationModal): missing toast dismiss on success distance donation
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-04 14:44:17 +02:00
33166bfafc 🚀RELEASE v1.4.3
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-04 14:32:56 +02:00
b2648645e8 revert: buggy pagination
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-04 14:32:37 +02:00
53e3ddb751 🚀RELEASE v1.4.2
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-04 13:55:14 +02:00
edc2dcab92 fix(DonorDetail): missing toast import
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-04 13:53:59 +02:00
d49f545d94 fix(GenerateRunnerCertificates): missing toast import
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-04 13:53:42 +02:00
3b98c99b72 🚀RELEASE v1.4.1
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-01 15:10:55 +02:00
1da775a09b Fixed translation 2023-05-01 15:10:18 +02:00
f0475bd9a0 🚀RELEASE v1.4.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-01 15:08:03 +02:00
15d8afefbb Bump dockerfile node
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-01 15:06:15 +02:00
f3bcc01685 Pinned config files used
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-01 15:04:58 +02:00
95238606d5 Switched build image node version
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-01 15:01:49 +02:00
bbf8170cb9 Updated store directory for dockerfil
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-01 14:59:36 +02:00
8c628f23dc Merge pull request 'next' (#180) from next into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #180
2023-05-01 12:55:19 +00:00
811f5d5754 monospace fonts for IDs in overviews 2023-05-01 14:53:34 +02:00
69ec7fc1fe ScansOverview full month formatting 2023-05-01 14:42:56 +02:00
064197d222 Dynamicly adjust page size for large datasets 2023-05-01 14:35:46 +02:00
e9cf2bc849 Full track table editing 2023-05-01 14:27:56 +02:00
103ad57ddc Added track update toasts 2023-05-01 14:25:22 +02:00
2856c5c1b7 Editing update logic 2023-05-01 14:23:25 +02:00
a953349c14 Updated the track editing switch logic 2023-05-01 14:13:40 +02:00
175d86745f Added edit button for trackscans 2023-05-01 14:05:44 +02:00
081a141218 Implemented delete for new track table 2023-05-01 14:03:46 +02:00
e904ab0b84 fix: a11y in About page 2023-04-27 08:30:12 +02:00
a2f9dbbe01 text cleanups, StatCard improvements 2023-04-27 08:29:07 +02:00
8b922309b9 drop gridjs (TracksOverview Actions will need to be re-implemented) 2023-04-26 23:26:07 +02:00
4c81e3c432 a11y improvements 2023-04-26 23:14:15 +02:00
6c1a707166 cleanup legacy TeamsOverview text 2023-04-26 23:12:03 +02:00
7d8253618b improved StatCard readability 2023-04-26 23:11:01 +02:00
dbaf85799a improved login 2023-04-26 23:09:47 +02:00
daeea24e0e drop middlename for admin users 2023-04-26 23:08:25 +02:00
0fca0352c5 a11y fix OrgOverview 2023-04-26 23:03:53 +02:00
8eaad8219a add missing striped tables 2023-04-26 23:02:13 +02:00
6bc92f4a08 a11y cleanup 2023-04-26 23:02:06 +02:00
8be40e2d80 translations 2023-04-26 22:58:17 +02:00
01b415d4cb fix: scan laptime formatting 2023-04-26 22:56:38 +02:00
5e82638f35 striped tabled 2023-04-26 22:51:57 +02:00
46d076af9d formatting, full migration to svelte-french-toast 2023-04-26 22:47:42 +02:00
38a665024e new license file version [CI SKIP] 2023-04-22 09:47:15 +00:00
d32eb8266b README update
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-22 11:45:47 +02:00
bc4ac0f316 drop legacy ThFilter components 2023-04-22 11:45:40 +02:00
6952b8727f drop propfilepic 2023-04-22 11:41:51 +02:00
81d4da6550 chore(deps): node:20.0.0 2023-04-22 11:39:32 +02:00
6154ca7ddf chore(deps): bump all 2023-04-22 11:39:14 +02:00
8fb1e0ca0f svelte-french-toast + translations
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-22 11:22:38 +02:00
763a01af09 cleanup MainDashContent 2023-04-22 11:16:57 +02:00
663cb29ccd translation cleanups 2023-04-22 11:16:50 +02:00
56c3365656 add svelte-french-toast
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-22 11:11:50 +02:00
e7b2c64798 🚀RELEASE v1.3.4
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 18:23:17 +02:00
7cb6b63eb9 Smaller sponsoring page size 2023-04-19 18:23:07 +02:00
d6d88f5f60 🚀RELEASE v1.3.3
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 18:18:44 +02:00
2c208c4381 bumped lfk-client-js 2023-04-19 18:18:26 +02:00
39bc6c4945 🚀RELEASE v1.3.2
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 18:17:18 +02:00
b94e3b745f fix(donors): Shortened texts 2023-04-19 18:16:50 +02:00
6f337aeee1 feat(donations): Resolve donations via donor 2023-04-19 18:15:15 +02:00
5d48060834 fix(donors): Removed debug infos 2023-04-19 18:12:24 +02:00
c842c203e2 🚀RELEASE v1.3.1
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-19 18:00:58 +02:00
5bcfc8db75 More filtering 2023-04-19 18:00:41 +02:00
27b4dde755 feat(donors): Added name and address filtering 2023-04-19 18:00:16 +02:00
91ab199769 feat(donations): Donation table filtering 2023-04-19 17:53:24 +02:00
151 changed files with 13289 additions and 12329 deletions

View File

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

View File

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

View File

@@ -1,101 +0,0 @@
---
kind: secret
name: docker_username
get:
path: odit-registry-builder
name: username
---
kind: secret
name: docker_password
get:
path: odit-registry-builder
name: password
---
kind: secret
name: git_ssh
get:
path: odit-git-bot
name: sshkey
---
kind: secret
name: npm_url
get:
path: odit-npm-cache
name: url
---
kind: pipeline
type: kubernetes
name: build:dev
steps:
- name: run full license export
depends_on: ["clone"]
image: registry.odit.services/hub/library/node:19.7.0-alpine3.16
commands:
- 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
settings:
branch: dev
commit: true
commit_message: new license file version [CI SKIP]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/frontend.git
ssh_key:
from_secret: git_ssh
- name: build dev
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:
- dev
cache: true
registry: registry.odit.services
trigger:
branch:
- dev
event:
- 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

View File

@@ -1,14 +1,12 @@
{ {
"recommendations": [ "recommendations": [
"2gua.rainbow-brackets", "2gua.rainbow-brackets",
"christian-kohler.npm-intellisense", "christian-kohler.npm-intellisense",
"remimarsal.prettier-now", "remimarsal.prettier-now",
"svelte.svelte-vscode", "svelte.svelte-vscode",
"lokalise.i18n-ally", "lokalise.i18n-ally",
"fivethree.vscode-svelte-snippets", "fivethree.vscode-svelte-snippets",
"voorjaar.windicss-intellisense" "voorjaar.windicss-intellisense"
], ],
"unwantedRecommendations": [ "unwantedRecommendations": ["antfu.i18n-ally"]
"antfu.i18n-ally" }
]
}

View File

@@ -1,7 +1,7 @@
languageIds: languageIds:
- javascript - javascript
- svelte - svelte
- html - html
monopoly: false monopoly: false
refactorTemplates: refactorTemplates:
- "{$_('$1')}" - "{$_('$1')}"

View File

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

42
.woodpecker/build.yml Normal file
View File

@@ -0,0 +1,42 @@
steps:
- name: run full license export
image: registry.odit.services/hub/library/node:19.7.0-alpine3.16
commands:
- npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8
- pnpm i
- pnpm licenses:export
secrets:
- source: odit-npm-cache-url
target: NPM_REGISTRY_URL
- name: push new licenses file to repo
image: appleboy/drone-git-push
settings:
branch: dev
commit: true
commit_message: new license file version [CI SKIP]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/frontend.git
ssh_key:
from_secret: odit-git-bot-sshkey
- name: build edge
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.odit.services/odit/website
tags:
- edge
registry: registry.odit.services
platforms: linux/amd64,linux/arm64
cache_from: registry.odit.services/odit/website:edge
username:
from_secret: odit-registry-builder-username
password:
from_secret: odit-registry-builder-password
secrets:
- source: odit-npm-cache-url
target: NPM_REGISTRY_URL
when:
event:
- push
branch:
- dev

20
.woodpecker/release.yml Normal file
View File

@@ -0,0 +1,20 @@
steps:
- name: build tag
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.odit.services/lfk/frontend
tags:
- "${CI_COMMIT_TAG}"
registry: registry.odit.services
platforms: linux/amd64,linux/arm64
cache_from: registry.odit.services/lfk/frontend:latest
username:
from_secret: odit-registry-builder-username
password:
from_secret: odit-registry-builder-password
secrets:
- source: odit-npm-cache-url
target: NPM_REGISTRY_URL
when:
event:
- tag

View File

@@ -2,9 +2,227 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.5.2](https://git.odit.services/lfk/frontend/compare/1.5.1...1.5.2)
- feat: improved dashboard titles ui + a11y [`21453ef`](https://git.odit.services/lfk/frontend/commit/21453ef272665c0b7c7b04009b7b74e110fbd988)
- feat: improved dashboard titles ui + a11y [`c883920`](https://git.odit.services/lfk/frontend/commit/c883920caaaaef30a8e54dd0e7eecd68943f3041)
- feat(dashboard): improved a11y of active sidebar menu item [`a50447f`](https://git.odit.services/lfk/frontend/commit/a50447f457ecc045995efb7b952b07ea09c91373)
- feat: improved mobile buttons + search ui [`38fb111`](https://git.odit.services/lfk/frontend/commit/38fb111f7a2b5a1a01b17b00e89ee081e4b91bd9)
- feat(i18n/de): rename "Track" to "Laufstrecke" [`1018243`](https://git.odit.services/lfk/frontend/commit/10182433f825968ee55298399b231173698a795c)
- feat(dashboard): improved mobile ui hamburger button [`b338f33`](https://git.odit.services/lfk/frontend/commit/b338f33a63ad8e98ab44deff2f80dbd5fe2a0fc2)
- feat(dashboard): match greeting style with rest of titles [`b1a2044`](https://git.odit.services/lfk/frontend/commit/b1a20446314d1b25e9f653bd2767b072fd629f97)
- feat(dashboard): add lfk icon and app name to mobile nav bar [`6bb49db`](https://git.odit.services/lfk/frontend/commit/6bb49db4eee95486f5a947d708b80a7a94d36933)
- feat(users/UsersOverview): improve ui by adding borders to badges [`cb82200`](https://git.odit.services/lfk/frontend/commit/cb82200481c629a0dd8b235821115ae4276948ca)
#### [1.5.1](https://git.odit.services/lfk/frontend/compare/1.5.0...1.5.1)
> 21 November 2024
- chore(deps): pnpm@9 [`35bec9f`](https://git.odit.services/lfk/frontend/commit/35bec9fe584b93cd52e8bab4e469713468a67f70)
- chore(deps): bump some [`8fae1fb`](https://git.odit.services/lfk/frontend/commit/8fae1fb6b3e033f789d2568cbd2640c0d163dc53)
- fix(scanstations): CopyScanStationTokenModal open after create [`372fa11`](https://git.odit.services/lfk/frontend/commit/372fa110ec402dae166a302f2209c79353983148)
- 🚀RELEASE v1.5.1 [`9abf74d`](https://git.odit.services/lfk/frontend/commit/9abf74d6d217e7745c1055bdbfbe97de7b14572f)
- fix(config): add explicit window.config [`91d2f46`](https://git.odit.services/lfk/frontend/commit/91d2f46b934bcba1429bd1d96e772c25c42a3e28)
- fix(dockerfile): AS casing [`50e81a6`](https://git.odit.services/lfk/frontend/commit/50e81a6cb5773381e153cbec3bed7db820ced84a)
- refactor(scanstations/CopyScanStationTokenModal): drop dispatch [`a5e72a1`](https://git.odit.services/lfk/frontend/commit/a5e72a18e368b5a7ee7b4e1894de613ecb767f28)
- chore(deps): node:23.2.0 [`93d67bd`](https://git.odit.services/lfk/frontend/commit/93d67bdba90a67b45d8895d9facaf66e908d53d6)
- fix(tracks/AddTrackModal): i18n [`c60bae4`](https://git.odit.services/lfk/frontend/commit/c60bae45334c2aa90d8931da07691c196469da46)
- fix: tailwind config [`43ac878`](https://git.odit.services/lfk/frontend/commit/43ac878d44b556c6d7811610f6fe0c9a5eff305f)
#### [1.5.0](https://git.odit.services/lfk/frontend/compare/1.4.13...1.5.0)
> 20 November 2024
- feat(ci)!: Switch to woodpecker [`fb8206f`](https://git.odit.services/lfk/frontend/commit/fb8206ff130f4f65dcf619a2a786e7d5895b77a1)
- 🚀RELEASE v1.5.0 [`ceabd06`](https://git.odit.services/lfk/frontend/commit/ceabd06a4319c3c9ffab680f909730d5bd789540)
- fix(components): Add missing toast imports [`9bfc0c5`](https://git.odit.services/lfk/frontend/commit/9bfc0c5338933e832d5df50457c7978c026d8df6)
#### [1.4.13](https://git.odit.services/lfk/frontend/compare/1.4.12...1.4.13)
> 31 July 2023
- 🚀RELEASE v1.4.13 [`dceb0ef`](https://git.odit.services/lfk/frontend/commit/dceb0ef46197dc56e29c5f52a5bd8f9fe9b70b27)
- Show donations as euro in export [`88bc198`](https://git.odit.services/lfk/frontend/commit/88bc1982cab4481e2e9245f81eff27e095b66a0f)
- new license file version [CI SKIP] [`6193eff`](https://git.odit.services/lfk/frontend/commit/6193eff38e1a9d5726bc7d572ab36b921de843d0)
#### [1.4.12](https://git.odit.services/lfk/frontend/compare/1.4.11...1.4.12)
> 18 May 2023
- 🚀RELEASE v1.4.12 [`65f1d22`](https://git.odit.services/lfk/frontend/commit/65f1d222050b0dec81fc847c1921b6135a55ce50)
- fix(donation/payment): Funny javascript number to float conversion where integers were needed [`d867c08`](https://git.odit.services/lfk/frontend/commit/d867c08aba234d3a7fe9e2311d37dc5e96fc2afc)
- new license file version [CI SKIP] [`08642d7`](https://git.odit.services/lfk/frontend/commit/08642d7618faeae31f0acfe776642c9fa156e5ff)
#### [1.4.11](https://git.odit.services/lfk/frontend/compare/1.4.10...1.4.11)
> 10 May 2023
- chore(deps): Lockfile [`f77460b`](https://git.odit.services/lfk/frontend/commit/f77460bb0c8ce6d0f3d83a077017d5fc7bf55af7)
- 🚀RELEASE v1.4.11 [`373484c`](https://git.odit.services/lfk/frontend/commit/373484c2424bea7ae0d70d342e0ae2076aab1b6a)
- feat(orgs): Show total distance [`574e0dc`](https://git.odit.services/lfk/frontend/commit/574e0dcb051305bde2fc76d8456a35baec0cf309)
- chore(deps): More bumps [`7b19a0a`](https://git.odit.services/lfk/frontend/commit/7b19a0aa08bb6c89c51d27c0d05777e8fcfdad17)
#### [1.4.10](https://git.odit.services/lfk/frontend/compare/1.4.9...1.4.10)
> 10 May 2023
- chore(deps): Bumped svelte-table [`29a2854`](https://git.odit.services/lfk/frontend/commit/29a2854671b3af5b85ea96d050a9076f47b6575d)
- 🚀RELEASE v1.4.10 [`c3e9c27`](https://git.odit.services/lfk/frontend/commit/c3e9c27cd3d4b916f1661d4958cabab038918587)
- chore(deps): Pin and bump [`8e6786e`](https://git.odit.services/lfk/frontend/commit/8e6786e72227b3f07cc805f0957d5b7fd123ec13)
- chore(deps): Bumped scanclientjs [`6ad4056`](https://git.odit.services/lfk/frontend/commit/6ad40564e3e342046f6ee19fab9e455ec3bbff9b)
#### [1.4.9](https://git.odit.services/lfk/frontend/compare/1.4.8...1.4.9)
> 9 May 2023
- 🚀RELEASE v1.4.9 [`776973b`](https://git.odit.services/lfk/frontend/commit/776973bfe9b34c26a1c80d5e458cc2644dd9036b)
- Changed the in table replacement method [`d9a47f8`](https://git.odit.services/lfk/frontend/commit/d9a47f882c1c6bcf98ef85d50d70c010d54b326e)
- Fixed empty return [`6025e43`](https://git.odit.services/lfk/frontend/commit/6025e43baa8516657a60a1de9a82c2189221c6ac)
#### [1.4.8](https://git.odit.services/lfk/frontend/compare/1.4.7...1.4.8)
> 9 May 2023
- Switched donor loading to non-paginated [`59fe2df`](https://git.odit.services/lfk/frontend/commit/59fe2dfabb224863876c4db31a965c34a51a9369)
- 🚀RELEASE v1.4.8 [`4235758`](https://git.odit.services/lfk/frontend/commit/4235758a6d1499715287d6ab193cc87c68d5742e)
#### [1.4.7](https://git.odit.services/lfk/frontend/compare/1.4.6...1.4.7)
> 4 May 2023
- Paginated modal data loading [`a8a7711`](https://git.odit.services/lfk/frontend/commit/a8a771114df6eb57d5b1d5497a5be49e619d4102)
- Moved loading to onmount [`4e0a2c8`](https://git.odit.services/lfk/frontend/commit/4e0a2c83015bde5e360c5fb2c0babbeaa03dc2b5)
- 🚀RELEASE v1.4.7 [`6364536`](https://git.odit.services/lfk/frontend/commit/6364536dcd840c71f7cb6afb31bbc4f160ac4f73)
#### [1.4.6](https://git.odit.services/lfk/frontend/compare/1.4.5...1.4.6)
> 4 May 2023
- 🚀RELEASE v1.4.6 [`b6fed92`](https://git.odit.services/lfk/frontend/commit/b6fed92a176af1c975484d9146ee5634e0031401)
- fix(donor/details): don't load donations [`a2ff5b8`](https://git.odit.services/lfk/frontend/commit/a2ff5b8a142ce4e6b8876f64935f9787ec44a51e)
- fix(donor/detail): Set email to null to avoid vaidation errors [`97b57ae`](https://git.odit.services/lfk/frontend/commit/97b57aeb0cc9058542a36dea9c8b2852169c250f)
- fix(donor/detail): Set phone to null to avoid vaidation errors [`e25ed1f`](https://git.odit.services/lfk/frontend/commit/e25ed1fff9b200605d5d2b78238b774ec7289aaa)
#### [1.4.5](https://git.odit.services/lfk/frontend/compare/1.4.4...1.4.5)
> 4 May 2023
- Revert "revert: buggy pagination" [`dacb2f8`](https://git.odit.services/lfk/frontend/commit/dacb2f8ace373f6594fc64af133971af053f00c0)
- fix: Removed dynamic pagesize adjustments [`803d64c`](https://git.odit.services/lfk/frontend/commit/803d64c78caa570d31d6055e70e2d2af6834f04b)
- 🚀RELEASE v1.4.5 [`0284f18`](https://git.odit.services/lfk/frontend/commit/0284f18beb8b24d4d4d071eca13bc5868666232c)
#### [1.4.4](https://git.odit.services/lfk/frontend/compare/1.4.3...1.4.4)
> 4 May 2023
- 🚀RELEASE v1.4.4 [`b7a5396`](https://git.odit.services/lfk/frontend/commit/b7a53960e5f37ae089d77bc11668d917145e2abb)
- fix(AddDonationModal): missing toast dismiss on success distance donation [`66f1e6b`](https://git.odit.services/lfk/frontend/commit/66f1e6b4fe1350ee79673a0aff97e36f44179c92)
#### [1.4.3](https://git.odit.services/lfk/frontend/compare/1.4.2...1.4.3)
> 4 May 2023
- revert: buggy pagination [`b264864`](https://git.odit.services/lfk/frontend/commit/b2648645e8fc05f8742ecfc592557f954261671b)
- 🚀RELEASE v1.4.3 [`33166bf`](https://git.odit.services/lfk/frontend/commit/33166bfafcffb9d86dfc7dfcd2cb8ba5c85da7e7)
#### [1.4.2](https://git.odit.services/lfk/frontend/compare/1.4.1...1.4.2)
> 4 May 2023
- 🚀RELEASE v1.4.2 [`53e3ddb`](https://git.odit.services/lfk/frontend/commit/53e3ddb751c1150a4640ae6302e4df5b88cedc51)
- fix(GenerateRunnerCertificates): missing toast import [`d49f545`](https://git.odit.services/lfk/frontend/commit/d49f545d94acabc0c96860f212466b7a4cbe7dab)
- fix(DonorDetail): missing toast import [`edc2dca`](https://git.odit.services/lfk/frontend/commit/edc2dcab92c3cace05335a283a849c3c978ec8ec)
#### [1.4.1](https://git.odit.services/lfk/frontend/compare/1.4.0...1.4.1)
> 1 May 2023
- 🚀RELEASE v1.4.1 [`3b98c99`](https://git.odit.services/lfk/frontend/commit/3b98c99b72f24b8552e2b2334f13622bdf6ef90d)
- Fixed translation [`1da775a`](https://git.odit.services/lfk/frontend/commit/1da775a09b8be90a49e06aed16df917d221ee989)
#### [1.4.0](https://git.odit.services/lfk/frontend/compare/1.3.4...1.4.0)
> 1 May 2023
- formatting, full migration to svelte-french-toast [`46d076a`](https://git.odit.services/lfk/frontend/commit/46d076af9d65ebb11504a7e6879753780b69db2c)
- drop gridjs (TracksOverview Actions will need to be re-implemented) [`8b92230`](https://git.odit.services/lfk/frontend/commit/8b922309b990c42fcfd57b939abacf4d8c99e638)
- 🚀RELEASE v1.4.0 [`f0475bd`](https://git.odit.services/lfk/frontend/commit/f0475bd9a08d99f58b4d3dce584cd6a3a8630e56)
- Added track update toasts [`103ad57`](https://git.odit.services/lfk/frontend/commit/103ad57ddc8a35ff971bef44053a9e32a7b68233)
- drop legacy ThFilter components [`bc4ac0f`](https://git.odit.services/lfk/frontend/commit/bc4ac0f3160571cd412361de82ef4555ee068677)
- text cleanups, StatCard improvements [`a2f9dbb`](https://git.odit.services/lfk/frontend/commit/a2f9dbbe014b5ae9705e8e7b6944f7f7c576d22e)
- Updated the track editing switch logic [`a953349`](https://git.odit.services/lfk/frontend/commit/a953349c1478b912e08f88c1fb70c74af0bc9bbb)
- chore(deps): bump all [`6154ca7`](https://git.odit.services/lfk/frontend/commit/6154ca7ddfb8b6ad0e1644b8c6756d51f2fbb858)
- svelte-french-toast + translations [`8fb1e0c`](https://git.odit.services/lfk/frontend/commit/8fb1e0ca0f51c90270fb5e1a05be5e8273238a2c)
- Implemented delete for new track table [`081a141`](https://git.odit.services/lfk/frontend/commit/081a141218ab7de2620f7b06083697368d44bf6c)
- striped tabled [`5e82638`](https://git.odit.services/lfk/frontend/commit/5e82638f3594298d0542cd03d5d6aa80aa383b9d)
- add svelte-french-toast [`56c3365`](https://git.odit.services/lfk/frontend/commit/56c33656562079bb773491c8aecedea3f6acdb74)
- Editing update logic [`2856c5c`](https://git.odit.services/lfk/frontend/commit/2856c5c1b786f732b0db80324ea74513e8be186d)
- monospace fonts for IDs in overviews [`811f5d5`](https://git.odit.services/lfk/frontend/commit/811f5d575496be43e5e48197813112d35e79a81f)
- Full track table editing [`e9cf2bc`](https://git.odit.services/lfk/frontend/commit/e9cf2bc8498fc02332059880d7a6994348165b76)
- drop propfilepic [`6952b87`](https://git.odit.services/lfk/frontend/commit/6952b8727f06c520cb60a00acfde1dff52d4f345)
- fix: scan laptime formatting [`01b415d`](https://git.odit.services/lfk/frontend/commit/01b415d4cb147879e959e86d053dc02cae8cfdc9)
- Dynamicly adjust page size for large datasets [`064197d`](https://git.odit.services/lfk/frontend/commit/064197d2226da772907099ecf96c3ab984c9af59)
- ScansOverview full month formatting [`69ec7fc`](https://git.odit.services/lfk/frontend/commit/69ec7fc1fecc67751643ce35f22925f3132b8792)
- translations [`8be40e2`](https://git.odit.services/lfk/frontend/commit/8be40e2d80336f72989deb3e5e20a7cd8f7fb6f1)
- drop middlename for admin users [`daeea24`](https://git.odit.services/lfk/frontend/commit/daeea24e0e99b8a95665167d62d0ee830bdea3de)
- add missing striped tables [`8eaad82`](https://git.odit.services/lfk/frontend/commit/8eaad8219a109fa8b4bd1f719d7079bff8b7c041)
- translation cleanups [`663cb29`](https://git.odit.services/lfk/frontend/commit/663cb29ccde4fa15317f764147187c5b82e748d5)
- Added edit button for trackscans [`175d867`](https://git.odit.services/lfk/frontend/commit/175d86745fb9bfce03fe5f5c638b52467b688938)
- a11y cleanup [`6bc92f4`](https://git.odit.services/lfk/frontend/commit/6bc92f4a080f0c506793866d99c97ccb87ba15b8)
- a11y fix OrgOverview [`0fca035`](https://git.odit.services/lfk/frontend/commit/0fca0352c59cdccb99716355591f88ff573ac949)
- README update [`d32eb82`](https://git.odit.services/lfk/frontend/commit/d32eb8266b0e9daec4b9ba52832d5e5118abec45)
- a11y improvements [`4c81e3c`](https://git.odit.services/lfk/frontend/commit/4c81e3c43218be4b23d137b386520c71d19f5844)
- cleanup legacy TeamsOverview text [`6c1a707`](https://git.odit.services/lfk/frontend/commit/6c1a70716665d57f1326c4475030ae15a7c459e0)
- fix: a11y in About page [`e904ab0`](https://git.odit.services/lfk/frontend/commit/e904ab0b8494ff57579c8954a8eb713fc223a88f)
- improved login [`dbaf857`](https://git.odit.services/lfk/frontend/commit/dbaf85799ac9e56d8760450cfe357df016f10da7)
- chore(deps): node:20.0.0 [`81d4da6`](https://git.odit.services/lfk/frontend/commit/81d4da655099100c631d450caafbf7039fa20592)
- cleanup MainDashContent [`763a01a`](https://git.odit.services/lfk/frontend/commit/763a01af09b36004ceccfa6b182b7dc5ea070128)
- Updated store directory for dockerfil [`bbf8170`](https://git.odit.services/lfk/frontend/commit/bbf8170cb98410bbcd8dc51bb122beee615312ee)
- Bump dockerfile node [`15d8afe`](https://git.odit.services/lfk/frontend/commit/15d8afefbb6b697a6cbdb2d803a7d8edcea4e650)
- Pinned config files used [`f3bcc01`](https://git.odit.services/lfk/frontend/commit/f3bcc01685f3ea3ef6786a8e7d9a5b1a4f829d53)
- Switched build image node version [`9523860`](https://git.odit.services/lfk/frontend/commit/95238606d52ca58985b91ea03f7e9f490fdf2310)
- Merge pull request 'next' (#180) from next into dev [`8c628f2`](https://git.odit.services/lfk/frontend/commit/8c628f23dcfb1f6f120d19bb3ecdb422ca5093cd)
- improved StatCard readability [`7d82536`](https://git.odit.services/lfk/frontend/commit/7d8253618b18719549824ed19e024b8828c9df06)
- new license file version [CI SKIP] [`38a6650`](https://git.odit.services/lfk/frontend/commit/38a665024eb1df3eba66c61d8cb3199000b629e5)
#### [1.3.4](https://git.odit.services/lfk/frontend/compare/1.3.3...1.3.4)
> 19 April 2023
- 🚀RELEASE v1.3.4 [`e7b2c64`](https://git.odit.services/lfk/frontend/commit/e7b2c647981111650b3e2e471f4b5195fa6b65b6)
- Smaller sponsoring page size [`7cb6b63`](https://git.odit.services/lfk/frontend/commit/7cb6b63eb9596da4ee84369b220c3e680c607032)
#### [1.3.3](https://git.odit.services/lfk/frontend/compare/1.3.2...1.3.3)
> 19 April 2023
- 🚀RELEASE v1.3.3 [`d6d88f5`](https://git.odit.services/lfk/frontend/commit/d6d88f5f60716ca496a17f09b835b23223ec495d)
- bumped lfk-client-js [`2c208c4`](https://git.odit.services/lfk/frontend/commit/2c208c438185892270a0ebd37deb6a7c9ac08fc0)
#### [1.3.2](https://git.odit.services/lfk/frontend/compare/1.3.1...1.3.2)
> 19 April 2023
- 🚀RELEASE v1.3.2 [`39bc6c4`](https://git.odit.services/lfk/frontend/commit/39bc6c49450964510f996369d014f92c569188ae)
- fix(donors): Shortened texts [`b94e3b7`](https://git.odit.services/lfk/frontend/commit/b94e3b745f2febbe91e16a7a26f96b47d347ab92)
- feat(donations): Resolve donations via donor [`6f337ae`](https://git.odit.services/lfk/frontend/commit/6f337aeee16267d1e67e3d3855b63b6f2e57979f)
- fix(donors): Removed debug infos [`5d48060`](https://git.odit.services/lfk/frontend/commit/5d48060834717b2244172a0914e2690f8fe634d9)
#### [1.3.1](https://git.odit.services/lfk/frontend/compare/1.3.0...1.3.1)
> 19 April 2023
- feat(donations): Donation table filtering [`91ab199`](https://git.odit.services/lfk/frontend/commit/91ab199769c9f4f8051c74ad43a701db321f3995)
- feat(donors): Added name and address filtering [`27b4dde`](https://git.odit.services/lfk/frontend/commit/27b4dde7551995c9d7e8ca33a9bd97d429a35801)
- 🚀RELEASE v1.3.1 [`c842c20`](https://git.odit.services/lfk/frontend/commit/c842c203e2fbf0a201297d475db9047c0691bd52)
- More filtering [`5bcfc8d`](https://git.odit.services/lfk/frontend/commit/5bcfc8db752fce96e9f523d14cefff1a4f675661)
#### [1.3.0](https://git.odit.services/lfk/frontend/compare/1.2.0...1.3.0) #### [1.3.0](https://git.odit.services/lfk/frontend/compare/1.2.0...1.3.0)
> 19 April 2023
- feat(donations): Implemented donation deletion via confirm modal [`505fb8c`](https://git.odit.services/lfk/frontend/commit/505fb8cb08b81a7dcb08561bdda0f6464f140d3e) - feat(donations): Implemented donation deletion via confirm modal [`505fb8c`](https://git.odit.services/lfk/frontend/commit/505fb8cb08b81a7dcb08561bdda0f6464f140d3e)
- 🚀RELEASE v1.3.0 [`e75be49`](https://git.odit.services/lfk/frontend/commit/e75be49be42c3d5581e2204bfa064bfa3778c1b6)
- feat(donationsoverview): Switched donations overview to datatable [`133470b`](https://git.odit.services/lfk/frontend/commit/133470b6f2a63ec087f27c98ef260648a8672e5f) - 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): 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(donations): Donations reactive create and load into datatable [`02003ec`](https://git.odit.services/lfk/frontend/commit/02003ec80efc16aabd126710a6eeac18df43f841)

View File

@@ -1,15 +1,16 @@
FROM registry.odit.services/hub/library/node:19.7.0-alpine3.16 as build FROM registry.odit.services/hub/library/node:23.2.0-alpine3.20 AS build
ARG NPM_REGISTRY_URL=https://registry.npmjs.org ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app WORKDIR /app
COPY package.json pnpm-lock.yaml *.config.js *.config.cjs index.html ./ COPY package.json pnpm-lock.yaml vite.config.js tailwind.config.cjs postcss.config.cjs index.html ./
RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8 && pnpm i RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@9
RUN mkdir /pnpm && pnpm config set store-dir /pnpm && pnpm i
COPY src ./src COPY src ./src
COPY public ./public COPY public ./public
RUN pnpm build RUN pnpm build
# final image # final image
FROM registry.odit.services/library/nginx-brotli:3.15 as final FROM registry.odit.services/library/nginx-brotli:3.15 AS final
COPY --from=build /app/dist /usr/share/nginx/html COPY --from=build /app/dist /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf COPY ./nginx.conf /etc/nginx/nginx.conf

View File

@@ -1,22 +1,27 @@
# @odit/lfk-frontend # @odit/lfk-frontend
## ✒️ Overview ## ✒️ Overview
This is an API client for [https://git.odit.services/lfk/backend](@lfk/backend) 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. This application is intended for use by admin users/ members only.
## 🚀 Getting Started ## 🚀 Getting Started
``` ```
yarn pnpm i
``` ```
## Development ## Development
``` ```
yarn dev pnpm dev
/ /
yarn dev --open pnpm dev --open
``` ```
## Build ## Build
```
pnpm build
``` ```
yarn build
```

View File

@@ -1,8 +1,8 @@
version: "3" version: "3"
services: services:
httpd: httpd:
build: . build: .
volumes: volumes:
- ./public/env.sample.js:/usr/share/nginx/html/env.js - ./public/env.sample.js:/usr/share/nginx/html/env.js
ports: ports:
- 4050:80 - 4050:80

View File

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

View File

@@ -1,16 +1,18 @@
import fs from 'fs' import fs from "fs";
// get all language files // get all language files
const files = fs.readdirSync('./src/locales/'); const files = fs.readdirSync("./src/locales/");
files.forEach((f) => { files.forEach((f) => {
// read file as object // read file as object
const unordered = JSON.parse(fs.readFileSync(`src/locales/${f}`)); const unordered = JSON.parse(fs.readFileSync(`src/locales/${f}`));
// order object by keys alpabetically A-Z // order object by keys alpabetically A-Z
const ordered = Object.keys(unordered).sort().reduce((obj, key) => { const ordered = Object.keys(unordered)
obj[key] = unordered[key]; .sort()
return obj; .reduce((obj, key) => {
}, {}); obj[key] = unordered[key];
// format output as json for commit diff compatibility return obj;
const out = JSON.stringify(ordered, 0, 4); }, {});
// write output file // format output as json for commit diff compatibility
fs.writeFileSync(`src/locales/${f}`, out); const out = JSON.stringify(ordered, 0, 4);
// write output file
fs.writeFileSync(`src/locales/${f}`, out);
}); });

View File

@@ -1,61 +1,63 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "1.3.0", "version": "1.5.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",
"dev": "vite", "dev": "vite",
"build": "vite build", "format": "prettier --write --plugin-search-dir=. .",
"release": "release-it", "build": "vite build",
"licenses:export": "license-exporter --json -o public" "release": "release-it",
}, "licenses:export": "license-exporter --json -o public"
"license": "CC-BY-NC-SA-4.0", },
"devDependencies": { "license": "CC-BY-NC-SA-4.0",
"@odit/license-exporter": "0.0.12", "devDependencies": {
"@sveltejs/vite-plugin-svelte": "2.0.4", "@odit/license-exporter": "0.2.0",
"auto-changelog": "2.4.0", "@sveltejs/vite-plugin-svelte": "2.1.1",
"autoprefixer": "10.4.14", "auto-changelog": "2.5.0",
"postcss": "8.4.21", "autoprefixer": "10.4.20",
"release-it": "15.10.1", "postcss": "8.4.49",
"svelte-select": "3.17.0", "prettier": "3.3.3",
"tailwindcss": "3.3.1", "prettier-plugin-svelte": "3.2.8",
"vite": "4.2.1" "release-it": "17.10.0",
}, "svelte-select": "3.17.0",
"release-it": { "tailwindcss": "3.4.15",
"git": { "vite": "4.3.3"
"commit": true, },
"requireCleanWorkingDir": false, "release-it": {
"commitMessage": "🚀RELEASE v${version}", "git": {
"push": true, "commit": true,
"tag": true, "requireCleanWorkingDir": false,
"tagName": null, "commitMessage": "🚀RELEASE v${version}",
"tagAnnotation": "v${version}" "push": true,
}, "tag": true,
"npm": { "tagName": null,
"publish": false "tagAnnotation": "v${version}"
}, },
"hooks": { "npm": {
"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" "publish": false
} },
}, "hooks": {
"dependencies": { "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"
"@odit/lfk-client-js": "1.1.0", }
"@paralleldrive/cuid2": "^2.2.0", },
"@tanstack/svelte-table": "^8.8.5", "dependencies": {
"bwip-js": "^3.4.0", "@odit/lfk-client-js": "1.1.3",
"check-password-strength": "2.0.7", "@paralleldrive/cuid2": "2.2.2",
"csvtojson": "2.0.10", "@tanstack/svelte-table": "8.9.1",
"gridjs": "3.4.0", "bwip-js": "3.4.0",
"localforage": "1.10.0", "check-password-strength": "2.0.10",
"marked": "2.0.3", "csvtojson": "2.0.10",
"svelte": "3.58.0", "localforage": "1.10.0",
"svelte-i18n": "3.6.0", "marked": "4.3.0",
"tinro": "0.6.12", "svelte": "3.58.0",
"toastify-js": "1.12.0", "svelte-french-toast": "1.0.4-beta.0",
"validator": "13.9.0", "svelte-i18n": "3.6.0",
"xlsx": "0.18.5" "tinro": "0.6.12",
}, "validator": "13.9.0",
"volta": { "xlsx": "0.18.5"
"node": "19.7.0" },
} "volta": {
"node": "20.0.0"
}
} }

5641
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -1,34 +1,39 @@
{ {
"name": "Lauf für Kaya! - Admin", "name": "Lauf für Kaya! - Admin",
"short_name": "LfK!Admin", "short_name": "LfK!Admin",
"start_url": "/?utm_source=pwa", "start_url": "/?utm_source=pwa",
"orientation": "portrait-primary", "orientation": "portrait-primary",
"display": "standalone", "display": "standalone",
"background_color": "#fff", "background_color": "#fff",
"theme_color": "#fff", "theme_color": "#fff",
"description": "Lauf für Kaya! - Admin", "description": "Lauf für Kaya! - Admin",
"shortcuts": [ "shortcuts": [
{ {
"name": "Users", "name": "Users",
"url": "/users/?utm_source=pwa" "url": "/users/?utm_source=pwa"
} }
], ],
"icons": [ "icons": [
{ {
"src": "/favicon.png", "src": "/favicon.png",
"sizes": "48x48", "sizes": "48x48",
"type": "image/png" "type": "image/png"
}, },
{ {
"src": "/favicon.png", "src": "/favicon.png",
"sizes": "144x144", "sizes": "144x144",
"type": "image/png" "type": "image/png"
}, },
{ {
"src": "/lfk-logo.png", "src": "/lfk-logo.png",
"sizes": "1540x144", "sizes": "1540x144",
"type": "image/png" "type": "image/png"
}, },
{ "src": "/maskable_icon_x1.png", "sizes": "750x750", "type": "image/png", "purpose": "any maskable" } {
] "src": "/maskable_icon_x1.png",
"sizes": "750x750",
"type": "image/png",
"purpose": "any maskable"
}
]
} }

View File

@@ -1 +1 @@
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. 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.

View File

@@ -1,6 +1,4 @@
<script> <script>
import "toastify-js/src/toastify.css";
import "gridjs/dist/theme/mermaid.css";
import { Route, router } from "tinro"; import { Route, router } from "tinro";
router.subscribe((routeInfo) => { router.subscribe((routeInfo) => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
@@ -24,11 +22,10 @@
name: "lfk_admin", name: "lfk_admin",
version: 1.0, version: 1.0,
storeName: "lfk_admin", storeName: "lfk_admin",
description: "LfK! admin dashbaord", description: "LfK! admin dashboard",
}); });
window.onunhandledrejection = (event) => { window.onunhandledrejection = (event) => {
if (event.reason.toString() == "Error: Unauthorized") { if (event.reason.toString() == "Error: Unauthorized") {
console.log("Found 1");
localForage.clear(); localForage.clear();
location.replace("/"); location.replace("/");
} }
@@ -72,29 +69,29 @@
import Scans from "./components/scans/Scans.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 Cards from "./components/cards/Cards.svelte";
import StatsClients from "./components/statsclients/StatsClients.svelte"; import StatsClients from "./components/statsclients/StatsClients.svelte";
import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte"; import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte";
store.init(); store.init();
</script> </script>
<Route> <Route>
{#if $router.path === '/forgot_password'} {#if $router.path === "/forgot_password"}
<Route path="/forgot_password"> <Route path="/forgot_password">
<ForgotPassword /> <ForgotPassword />
</Route> </Route>
{:else if $router.path.includes('/reset')} {:else if $router.path.includes("/reset")}
<Route path="/reset/:resetkey" let:params> <Route path="/reset/:resetkey" let:params>
<ResetPassword {params} /> <ResetPassword {params} />
</Route> </Route>
{:else if $router.path === '/about'} {:else if $router.path === "/about"}
<Route path="/about"> <Route path="/about">
<About /> <About />
</Route> </Route>
{:else if $router.path === '/imprint'} {:else if $router.path === "/imprint"}
<Route path="/imprint"> <Route path="/imprint">
<Imprint /> <Imprint />
</Route> </Route>
{:else if $router.path === '/privacy'} {:else if $router.path === "/privacy"}
<Route path="/privacy"> <Route path="/privacy">
<Privacy /> <Privacy />
</Route> </Route>

View File

@@ -1,28 +1,22 @@
<script> <script>
import { ApiError, AuthService } from "@odit/lfk-client-js"; import { AuthService } from "@odit/lfk-client-js";
import toast from "svelte-french-toast";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import "toastify-js/src/toastify.css";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
let reset_mail_sent = false; let reset_mail_sent = false;
let usersEmail = ""; let usersEmail = "";
function reset() { function reset() {
if (isEmail(usersEmail)) { if (isEmail(usersEmail)) {
toast.loading($_("mail-validation-in-progress"));
AuthService.authControllerGetResetToken("de", { email: usersEmail }) AuthService.authControllerGetResetToken("de", { email: usersEmail })
.then((resp) => { .then((resp) => {
Toastify({ toast.dismiss();
text: $_("mail-validation-in-progress"),
duration: 3500,
}).showToast();
reset_mail_sent = true; reset_mail_sent = true;
}) })
.catch((err) => {}); .catch((err) => {});
} else { } else {
Toastify({ toast($_("invalid-mail-reset"));
text: $_("invalid-mail-reset"),
duration: 3500,
}).showToast();
} }
} }
</script> </script>
@@ -32,17 +26,18 @@
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900"> <p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')} {$_("application_name")}
</p> </p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900"> <p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_('password-reset-mail-sent', { values: { usersEmail: usersEmail } })} {$_("password-reset-mail-sent", { values: { usersEmail: usersEmail } })}
</p> </p>
<div class="mt-6"> <div class="mt-6">
<div class="mt-6"> <div class="mt-6">
<a <a
href="/" href="/"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"> class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
{$_('goback')} >
{$_("goback")}
</a> </a>
</div> </div>
</div> </div>
@@ -53,25 +48,26 @@
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900"> <p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')} {$_("application_name")}
</p> </p>
<p class="mt-6 text-sm text-center text-gray-900"> <p class="mt-6 text-sm text-center text-gray-900">
{$_('forgot_password')} {$_("forgot_password")}
</p> </p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900"> <p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_('dont-panic-were-resetting-it')} {$_("dont-panic-were-resetting-it")}
</p> </p>
<div> <div>
<div class="rounded-md shadow-sm"> <div class="rounded-md shadow-sm">
<div> <div>
<input <input
aria-label={$_('e-mail-adress')} aria-label={$_("e-mail-adress")}
name="email" name="email"
type="email" type="email"
required="" required=""
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border text-gray-900 rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm" class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border text-gray-900 rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
placeholder={$_('e-mail-adress')} placeholder={$_("e-mail-adress")}
bind:value={usersEmail} /> bind:value={usersEmail}
/>
</div> </div>
</div> </div>
@@ -79,19 +75,22 @@
<button <button
on:click={reset} on:click={reset}
type="submit" type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"> class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"
>
<span class="absolute left-0 inset-y pl-3"> <span class="absolute left-0 inset-y pl-3">
<svg <svg
class="h-5 w-5 text-gray-500" class="h-5 w-5 text-gray-500"
fill="currentColor" fill="currentColor"
viewBox="0 0 20 20"> viewBox="0 0 20 20"
>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd" /> clip-rule="evenodd"
/>
</svg> </svg>
</span> </span>
{$_('reset-my-password')} {$_("reset-my-password")}
</button> </button>
</div> </div>
<div class="mt-6"> <div class="mt-6">
@@ -100,24 +99,30 @@
<div class="w-full border-t border-gray-300" /> <div class="w-full border-t border-gray-300" />
</div> </div>
<div class="relative flex justify-center text-sm"> <div class="relative flex justify-center text-sm">
<span <span class="px-2 bg-gray-100 text-gray-500"
class="px-2 bg-gray-100 text-gray-500">{$_('dont-have-your-email-connected')}</span> >{$_("dont-have-your-email-connected")}</span
>
</div> </div>
</div> </div>
<span <span
class="mt-2 text-sm px-2 bg-gray-100 text-gray-500 justify-center relative flex">{$_('cannot-reset-your-password-directly')}</span> class="mt-2 text-sm px-2 bg-gray-100 text-gray-500 justify-center relative flex"
>{$_("cannot-reset-your-password-directly")}</span
>
<div class="mt-6"> <div class="mt-6">
<a <a
href="mailto:lfk@odit.services" href="mailto:lfk@odit.services"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"> class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
{$_('send-a-mail-to-lfk-odit-services')} >
{$_("send-a-mail-to-lfk-odit-services")}
</a> </a>
</div> </div>
<div class="mt-6"> <div class="mt-6">
<a <a
href="/" href="/"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm">{$_('goback')}</a> class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
>{$_("goback")}</a
>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -6,7 +6,7 @@
import { OpenAPI, AuthService } from "@odit/lfk-client-js"; import { OpenAPI, AuthService } from "@odit/lfk-client-js";
import Footer from "../general/Footer.svelte"; import Footer from "../general/Footer.svelte";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
import Toastify from "toastify-js"; import toast from "svelte-french-toast";
// ------ // ------
let username = config.default_username || ""; let username = config.default_username || "";
let password = config.default_password || ""; let password = config.default_password || "";
@@ -20,11 +20,7 @@
OpenAPI.TOKEN = value.access_token; OpenAPI.TOKEN = value.access_token;
const jwtinfo = JSON.parse(atob(OpenAPI.TOKEN.split(".")[1])); const jwtinfo = JSON.parse(atob(OpenAPI.TOKEN.split(".")[1]));
store.login(value, jwtinfo); store.login(value, jwtinfo);
Toastify({ toast($_("welcome_wavinghand"));
text: $_("welcome_wavinghand"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
} }
} }
}); });
@@ -33,10 +29,7 @@
// prevent login button spamming // prevent login button spamming
if (last_loginclick_processed && is_blocked_by_autologin === false) { if (last_loginclick_processed && is_blocked_by_autologin === false) {
last_loginclick_processed = false; last_loginclick_processed = false;
Toastify({ toast.loading($_("login_is_checked"));
text: $_("login_is_checked"),
duration: 500,
}).showToast();
let postdata = {}; let postdata = {};
if (isEmail(username)) { if (isEmail(username)) {
postdata = { postdata = {
@@ -56,31 +49,19 @@
const jwtinfo = JSON.parse(atob(OpenAPI.TOKEN.split(".")[1])); const jwtinfo = JSON.parse(atob(OpenAPI.TOKEN.split(".")[1]));
store.login(result.access_token, jwtinfo); store.login(result.access_token, jwtinfo);
location.replace("/"); location.replace("/");
Toastify({ toast.dismiss();
text: $_("welcome_wavinghand"), toast($_("welcome_wavinghand"));
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}) })
.catch((err) => { .catch((err) => {
Toastify({ toast.dismiss();
text: $_("error_on_login"), toast.error($_("error_on_login"));
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
}) })
.finally(() => { .finally(() => {
last_loginclick_processed = true; last_loginclick_processed = true;
}); });
// last login was not processed yet // last login was not processed yet
} else { } else {
Toastify({ toast($_("please-wait-a-moment-your-login-is-still-being-processed"));
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%)",
}).showToast();
} }
}; };
function handleKeydown(e) { function handleKeydown(e) {
@@ -91,34 +72,37 @@
</script> </script>
<div <div
class="min-h-screen flex items-center justify-center bg-gray-100 text-gray-900"> class="min-h-screen flex items-center justify-center bg-gray-100 text-gray-900"
>
<div class="max-w-md w-full py-12 px-6" role="main"> <div class="max-w-md w-full py-12 px-6" role="main">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold">{$_('application_name')}</p> <p class="mt-6 text-xl text-center font-bold">{$_("application_name")}</p>
<p class="mt-6 text-sm text-center">{$_('log_in_to_your_account')}</p> <p class="mt-2 mb-6 text-sm text-center">{$_("log_in_to_your_account")}</p>
<div> <div>
<div class="rounded-md shadow-sm"> <div class="rounded-md shadow-sm">
<div> <div>
<!-- svelte-ignore a11y-autofocus --> <!-- svelte-ignore a11y-autofocus -->
<input <input
autofocus autofocus
aria-label={$_('email_address_or_username')} aria-label={$_("email_address_or_username")}
type="text" type="text"
required="" required=""
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm" class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
on:keydown={handleKeydown} on:keydown={handleKeydown}
placeholder={$_('email_address_or_username')} placeholder={$_("email_address_or_username")}
bind:value={username} /> bind:value={username}
/>
</div> </div>
<div class="-mt-px relative"> <div class="-mt-px relative">
<input <input
aria-label={$_('password')} aria-label={$_("password")}
type="password" type="password"
required="" required=""
bind:value={password} bind:value={password}
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-b-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm" class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-b-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
on:keydown={handleKeydown} on:keydown={handleKeydown}
placeholder={$_('password')} /> placeholder={$_("password")}
/>
</div> </div>
</div> </div>
@@ -126,29 +110,33 @@
<button <button
on:click={login} on:click={login}
type="submit" type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"> class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"
>
<span class="absolute left-0 inset-y pl-3"> <span class="absolute left-0 inset-y pl-3">
<svg <svg
class="h-5 w-5 text-gray-500" class="h-5 w-5 text-gray-500"
fill="currentColor" fill="currentColor"
viewBox="0 0 20 20"> viewBox="0 0 20 20"
>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd" /> clip-rule="evenodd"
/>
</svg> </svg>
</span> </span>
{$_('log_in')} {$_("log_in")}
</button> </button>
</div> </div>
</div> </div>
<div class="mt-2"> <!-- <div class="mt-2">
<a <a
href="/forgot_password" href="/forgot_password"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"> class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
{$_('forgot_password')} >
{$_("forgot_password")}
</a> </a>
</div> </div> -->
</div> </div>
</div> </div>
<Footer /> <Footer />

View File

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

View File

@@ -1,8 +1,7 @@
<script> <script>
import { AuthService } from "@odit/lfk-client-js"; import { AuthService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import Toastify from "toastify-js"; import toast from "svelte-french-toast";
import "toastify-js/src/toastify.css";
import PasswordStrength, { import PasswordStrength, {
password_strong_enough, password_strong_enough,
} from "../auth/PasswordStrength.svelte"; } from "../auth/PasswordStrength.svelte";
@@ -11,101 +10,97 @@
export let params; export let params;
function set_new_password() { function set_new_password() {
if (password.trim() !== "") { if (password.trim() !== "") {
Toastify({ toast.loading($_("password-reset-in-progress"));
text: $_("password-reset-in-progress"),
duration: 3500,
}).showToast();
AuthService.authControllerResetPassword(atob(params.resetkey), { AuthService.authControllerResetPassword(atob(params.resetkey), {
password, password,
}) })
.then((resp) => { .then((resp) => {
Toastify({ toast.dismiss();
text: $_("password-reset-successful"), toast($_("password-reset-successful"));
duration: 3500,
}).showToast();
state = "reset_success"; state = "reset_success";
}) })
.catch((err) => { .catch((err) => {
state = "reset_error"; state = "reset_error";
}); });
} else { } else {
Toastify({ toast.dismiss();
text: $_("please-provide-a-password"), toast.error($_("please-provide-a-password"));
duration: 3500,
}).showToast();
} }
} }
</script> </script>
{#if state === 'reset_success'} {#if state === "reset_success"}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900"> <p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')} {$_("application_name")}
</p> </p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold"> <p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold">
{$_('successful-password-reset')} {$_("successful-password-reset")}
</p> </p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900"> <p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_('you-can-now-use-your-new-password-to-log-in-to-your-account')} {$_("you-can-now-use-your-new-password-to-log-in-to-your-account")}
</p> </p>
<div class="mt-6"> <div class="mt-6">
<div class="mt-6"> <div class="mt-6">
<a <a
href="/login/" href="/login/"
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"> 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"
{$_('go-to-login')} >
{$_("go-to-login")}
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{:else if state === 'reset_error'} {:else if state === "reset_error"}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900"> <p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')} {$_("application_name")}
</p> </p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold"> <p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold">
{$_('password-reset-failed')} {$_("password-reset-failed")}
</p> </p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900"> <p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_('please-request-a-new-reset-mail')} {$_("please-request-a-new-reset-mail")}
</p> </p>
<div class="mt-6"> <div class="mt-6">
<div class="mt-6"> <div class="mt-6">
<a <a
href="/forgot_password/" 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"> 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')} >
{$_("request-a-new-reset-mail")}
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{:else if state === 'reset_in_progress'} {:else if state === "reset_in_progress"}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900"> <p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')} {$_("application_name")}
</p> </p>
<p class="mt-2 mb-4 text-md text-center text-gray-900"> <p class="mt-2 mb-4 text-md text-center text-gray-900">
{$_('reset-password')} {$_("reset-password")}
</p> </p>
<div> <div>
<div class="rounded-md shadow-sm"> <div class="rounded-md shadow-sm">
<div> <div>
<input <input
aria-label={$_('new-password')} aria-label={$_("new-password")}
name="password" name="password"
type="password" type="password"
required="" required=""
class="border-gray-300 placeholder-gray-500 appearance-none rounded-md relative block w-full px-3 py-2 border text-gray-900 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm" class="border-gray-300 placeholder-gray-500 appearance-none rounded-md relative block w-full px-3 py-2 border text-gray-900 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
placeholder={$_('new-password')} placeholder={$_("new-password")}
bind:value={password} /> bind:value={password}
/>
</div> </div>
<PasswordStrength bind:password_change={password} /> <PasswordStrength bind:password_change={password} />
</div> </div>
@@ -116,19 +111,22 @@
disabled={!password_strong_enough(password)} disabled={!password_strong_enough(password)}
class:opacity-50={!password_strong_enough(password)} class:opacity-50={!password_strong_enough(password)}
type="submit" type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"> class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"
>
<span class="absolute left-0 inset-y pl-3"> <span class="absolute left-0 inset-y pl-3">
<svg <svg
class="h-5 w-5 text-gray-500" class="h-5 w-5 text-gray-500"
fill="currentColor" fill="currentColor"
viewBox="0 0 20 20"> viewBox="0 0 20 20"
>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd" /> clip-rule="evenodd"
/>
</svg> </svg>
</span> </span>
{$_('reset-my-password')} {$_("reset-my-password")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,274 +0,0 @@
<!--
This example requires Tailwind CSS v2.0+
This example requires some changes to your config:
```
// tailwind.config.js
module.exports = {
// ...
plugins: [
// ...
require('@tailwindcss/forms'),
]
}
```
-->
<div>
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">Profile</h3>
<p class="mt-1 text-sm text-gray-600">
This information will be displayed publicly so be careful what you share.
</p>
</div>
</div>
<div class="mt-5 md:mt-0 md:col-span-2">
<form action="#" method="POST">
<div class="shadow sm:rounded-md sm:overflow-hidden">
<div class="px-4 py-5 bg-white space-y-6 sm:p-6">
<div class="grid grid-cols-3 gap-6">
<div class="col-span-3 sm:col-span-2">
<label for="company_website" class="block text-sm font-medium text-gray-700">
Website
</label>
<div class="mt-1 flex rounded-md shadow-sm">
<span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 text-sm">
http://
</span>
<input type="text" name="company_website" id="company_website" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-r-md sm:text-sm border-gray-300" placeholder="www.example.com">
</div>
</div>
</div>
<div>
<label for="about" class="block text-sm font-medium text-gray-700">
About
</label>
<div class="mt-1">
<textarea id="about" name="about" rows="3" class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="you@example.com"></textarea>
</div>
<p class="mt-2 text-sm text-gray-500">
Brief description for your profile. URLs are hyperlinked.
</p>
</div>
<div>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block text-sm font-medium text-gray-700">
Photo
</label>
<div class="mt-2 flex items-center">
<span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">
<svg class="h-full w-full text-gray-300" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</span>
<button type="button" class="ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Change
</button>
</div>
</div>
<div>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block text-sm font-medium text-gray-700">
Cover photo
</label>
<div class="mt-2 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
<div class="space-y-1 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div class="flex text-sm text-gray-600">
<label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
<span>Upload a file</span>
<input id="file-upload" name="file-upload" type="file" class="sr-only">
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs text-gray-500">
PNG, JPG, GIF up to 10MB
</p>
</div>
</div>
</div>
</div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Save
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="hidden sm:block" aria-hidden="true">
<div class="py-5">
<div class="border-t border-gray-200"></div>
</div>
</div>
<div class="mt-10 sm:mt-0">
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">Personal Information</h3>
<p class="mt-1 text-sm text-gray-600">
Use a permanent address where you can receive mail.
</p>
</div>
</div>
<div class="mt-5 md:mt-0 md:col-span-2">
<form action="#" method="POST">
<div class="shadow overflow-hidden sm:rounded-md">
<div class="px-4 py-5 bg-white sm:p-6">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<label for="first_name" class="block text-sm font-medium text-gray-700">First name</label>
<input type="text" name="first_name" id="first_name" autocomplete="given-name" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-3">
<label for="last_name" class="block text-sm font-medium text-gray-700">Last name</label>
<input type="text" name="last_name" id="last_name" autocomplete="family-name" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-4">
<label for="email_address" class="block text-sm font-medium text-gray-700">Email address</label>
<input type="text" name="email_address" id="email_address" autocomplete="email" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-3">
<label for="country" class="block text-sm font-medium text-gray-700">Country / Region</label>
<select id="country" name="country" autocomplete="country" class="mt-1 block w-full 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">
<option>United States</option>
<option>Canada</option>
<option>Mexico</option>
</select>
</div>
<div class="col-span-6">
<label for="street_address" class="block text-sm font-medium text-gray-700">Street address</label>
<input type="text" name="street_address" id="street_address" autocomplete="street-address" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-6 lg:col-span-2">
<label for="city" class="block text-sm font-medium text-gray-700">City</label>
<input type="text" name="city" id="city" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-3 lg:col-span-2">
<label for="state" class="block text-sm font-medium text-gray-700">State / Province</label>
<input type="text" name="state" id="state" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-3 lg:col-span-2">
<label for="postal_code" class="block text-sm font-medium text-gray-700">ZIP / Postal</label>
<input type="text" name="postal_code" id="postal_code" autocomplete="postal-code" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
</div>
</div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Save
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="hidden sm:block" aria-hidden="true">
<div class="py-5">
<div class="border-t border-gray-200"></div>
</div>
</div>
<div class="mt-10 sm:mt-0">
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">Notifications</h3>
<p class="mt-1 text-sm text-gray-600">
Decide which communications you'd like to receive and how.
</p>
</div>
</div>
<div class="mt-5 md:mt-0 md:col-span-2">
<form action="#" method="POST">
<div class="shadow overflow-hidden sm:rounded-md">
<div class="px-4 py-5 bg-white space-y-6 sm:p-6">
<fieldset>
<legend class="text-base font-medium text-gray-900">By Email</legend>
<div class="mt-4 space-y-4">
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="comments" name="comments" type="checkbox" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded">
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-medium text-gray-700">Comments</label>
<p class="text-gray-500">Get notified when someones posts a comment on a posting.</p>
</div>
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="candidates" name="candidates" 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="candidates" class="font-medium text-gray-700">Candidates</label>
<p class="text-gray-500">Get notified when a candidate applies for a job.</p>
</div>
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="offers" name="offers" 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="offers" class="font-medium text-gray-700">Offers</label>
<p class="text-gray-500">Get notified when a candidate accepts or rejects an offer.</p>
</div>
</div>
</div>
</fieldset>
<fieldset>
<div>
<legend class="text-base font-medium text-gray-900">Push Notifications</legend>
<p class="text-sm text-gray-500">These are delivered via SMS to your mobile phone.</p>
</div>
<div class="mt-4 space-y-4">
<div class="flex items-center">
<input id="push_everything" name="push_notifications" type="radio" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300">
<label for="push_everything" class="ml-3 block text-sm font-medium text-gray-700">
Everything
</label>
</div>
<div class="flex items-center">
<input id="push_email" name="push_notifications" type="radio" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300">
<label for="push_email" class="ml-3 block text-sm font-medium text-gray-700">
Same as email
</label>
</div>
<div class="flex items-center">
<input id="push_nothing" name="push_notifications" type="radio" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300">
<label for="push_nothing" class="ml-3 block text-sm font-medium text-gray-700">
No push notifications
</label>
</div>
</div>
</fieldset>
</div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Save
</button>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@@ -4,19 +4,22 @@
<body class="antialiased font-sans"> <body class="antialiased font-sans">
<div class="flex min-h-screen"> <div class="flex min-h-screen">
<div class="w-full bg-white flex items-center justify-center "> <div class="w-full bg-white flex items-center justify-center">
<div class="max-w-sm m-8"> <div class="max-w-sm m-8">
<div class="text-black text-5xl md:text-15xl font-black"> <div class="text-black text-5xl md:text-15xl font-black">
{$_('internal-error')} {$_("internal-error")}
</div> </div>
<div class="w-16 h-1 bg-purple-light my-3 md:my-6" /> <div class="w-16 h-1 bg-purple-light my-3 md:my-6" />
<p <p
class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal"> class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal"
{$_('generic-ui-logic-error')} >
{$_("generic-ui-logic-error")}
</p> </p>
<a <a
href="/" href="/"
class="bg-transparent text-grey-darkest font-bold uppercase tracking-wide py-3 px-6 border-2 border-grey-light hover:border-grey rounded-lg">{$_('goback')}</a> class="bg-transparent text-grey-darkest font-bold uppercase tracking-wide py-3 px-6 border-2 border-grey-light hover:border-grey rounded-lg"
>{$_("goback")}</a
>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,7 +5,7 @@
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> <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"> <span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b> <b class="capitalize">{$_("general_promise_error")}</b>
{error} {error}
</span> </span>
</div> </div>

View File

@@ -1,24 +1,25 @@
export function getlang(langkeys) { export function getlang(langkeys) {
return { return {
search: { search: {
placeholder: langkeys.search placeholder: langkeys.search,
}, },
sort: { sort: {
sortAsc: langkeys.sort_column_ascending, sortAsc: langkeys.sort_column_ascending,
sortDesc: langkeys.sort_column_descending sortDesc: langkeys.sort_column_descending,
}, },
pagination: { pagination: {
previous: langkeys.previous, previous: langkeys.previous,
next: langkeys.next, next: langkeys.next,
navigate: (page, pages) => `${langkeys.page} ${page} ${langkeys.of} ${pages}`, navigate: (page, pages) =>
page: (page) => `${langkeys.page} ${page}`, `${langkeys.page} ${page} ${langkeys.of} ${pages}`,
showing: langkeys.showing, page: (page) => `${langkeys.page} ${page}`,
of: langkeys.of, showing: langkeys.showing,
to: langkeys.to, of: langkeys.of,
results: langkeys.records to: langkeys.to,
}, results: langkeys.records,
loading: langkeys.loading, },
noRecordsFound: langkeys.no_matching_records_found, loading: langkeys.loading,
error: langkeys.an_error_happened_while_fetching_the_data noRecordsFound: langkeys.no_matching_records_found,
}; error: langkeys.an_error_happened_while_fetching_the_data,
};
} }

View File

@@ -3,4 +3,4 @@
Or as others may call it: Real big bullshit time. Or as others may call it: Real big bullshit time.
Issue: https://git.odit.services/lfk/frontend/issues/136 Issue: https://git.odit.services/lfk/frontend/issues/136
--> -->
<div class="opacity-50"></div> <div class="opacity-50" />

View File

@@ -1,10 +1,10 @@
/** Dispatch event on click outside of node */ /** Dispatch event on click outside of node */
export function clickOutside(node) { export function clickOutside(node) {
const handleClick = (event) => { const handleClick = (event) => {
if (event.target.getAttribute('data-id') === 'modal_backdrop') { if (event.target.getAttribute("data-id") === "modal_backdrop") {
node.dispatchEvent(new CustomEvent('click_outside', node)); node.dispatchEvent(new CustomEvent("click_outside", node));
} }
}; };
document.removeEventListener('click', handleClick, true); document.removeEventListener("click", handleClick, true);
document.addEventListener('click', handleClick, true); document.addEventListener("click", handleClick, true);
} }

File diff suppressed because one or more lines are too long

View File

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

View File

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

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

View File

@@ -1,54 +1,53 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import store from "../../store"; import store from "../../store";
import AddCardBulkModal from "./AddCardBulkModal.svelte"; import AddCardBulkModal from "./AddCardBulkModal.svelte";
import AddCardModal from "./AddCardModal.svelte"; import AddCardModal from "./AddCardModal.svelte";
import CardsOverview from "./CardsOverview.svelte"; import CardsOverview from "./CardsOverview.svelte";
$: current_cards = []; $: current_cards = [];
export let modal_open = false; export let modal_open = false;
export let bulk_modal_open = false; export let bulk_modal_open = false;
let addCards; let addCards;
</script> </script>
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("cards")} {$_("cards")}
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:CREATE")} </h4>
<button {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:CREATE")}
on:click={() => { <button
modal_open = true; 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" 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:w-auto sm:text-sm mt-1 sm:mt-0"
{$_("add-card")} >
</button> {$_("add-card")}
<button </button>
on:click={() => { <button
bulk_modal_open = true; 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" 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:w-auto sm:text-sm mt-1 sm:mt-0"
{$_("create-bulk-cards")} >
</button> {$_("create-bulk-cards")}
{/if} </button>
</span> {/if}
<CardsOverview bind:current_cards bind:addCards /> <CardsOverview bind:current_cards bind:addCards />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:CREATE")}
<AddCardModal <AddCardModal
bind:modal_open bind:modal_open
on:created={(event) => { on:created={(event) => {
console.log(event) addCards(event.detail.cards);
addCards(event.detail.cards); }}
}} />
/> <AddCardBulkModal
<AddCardBulkModal bind:bulk_modal_open
bind:bulk_modal_open on:created={(event) => {
on:created={(event) => { addCards(event.detail.cards);
addCards(event.detail.cards); }}
}} />
/> {/if}
{/if}

View File

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

View File

@@ -1,315 +1,319 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { RunnerCardService } from "@odit/lfk-client-js"; import { RunnerCardService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import Toastify from "toastify-js"; import toast from "svelte-french-toast";
import CardsEmptyState from "./CardsEmptyState.svelte"; import CardsEmptyState from "./CardsEmptyState.svelte";
import CardDetailModal from "./CardDetailModal.svelte"; import CardDetailModal from "./CardDetailModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import InputElement from "../shared/InputElement.svelte"; import InputElement from "../shared/InputElement.svelte";
import { import {
createSvelteTable, createSvelteTable,
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
getFilteredRowModel, getFilteredRowModel,
getPaginationRowModel, getPaginationRowModel,
getSortedRowModel, getSortedRowModel,
renderComponent, renderComponent,
} from "@tanstack/svelte-table"; } from "@tanstack/svelte-table";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import TableBottom from "../shared/TableBottom.svelte"; import TableBottom from "../shared/TableBottom.svelte";
import TableActions from "../shared/TableActions.svelte"; import TableActions from "../shared/TableActions.svelte";
import TableHeader from "../shared/TableHeader.svelte"; import TableHeader from "../shared/TableHeader.svelte";
import CardStatus from "./CardStatus.svelte"; import CardStatus from "./CardStatus.svelte";
import CardRunner from "./CardRunner.svelte"; import CardRunner from "./CardRunner.svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { runnerFilter, statusFilter } from "../shared/tablefilters"; import { runnerFilter, statusFilter } from "../shared/tablefilters";
import DeleteCardModal from "./DeleteCardModal.svelte"; import DeleteCardModal from "./DeleteCardModal.svelte";
export let edit_modal_open = false; export let edit_modal_open = false;
export let runner = {}; export let runner = {};
export let editable = {}; export let editable = {};
export let original_data = {}; export let original_data = {};
export let current_cards = []; export let current_cards = [];
export const addCards = (cards) => { export const addCards = (cards) => {
console.log(cards); current_cards = current_cards.concat(...cards);
current_cards = current_cards.concat(...cards); options.update((options) => ({
options.update((options) => ({ ...options,
...options, data: current_cards,
data: current_cards, }));
})); };
};
$: dataLoaded = false;
$: dataLoaded = false; $: selected =
$: selected = $table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$table?.getSelectedRowModel().rows.map((row) => row.index) || []; $: selectedCards =
$: selectedCards = $table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$table?.getSelectedRowModel().rows.map((row) => row.original) || []; $: active_delete = undefined;
$: active_delete = undefined; $: cards_show = generate_cards.length > 0;
$: cards_show = generate_cards.length > 0; $: generate_cards = [];
$: generate_cards = [];
const columns = [
const columns = [ {
{ accessorKey: "code",
accessorKey: "code", header: () => $_("code"),
header: () => $_("code"), filterFn: `includesString`,
filterFn: `includesString`, },
}, {
{ accessorKey: "runner",
accessorKey: "runner", header: () => $_("runner"),
header: () => $_("runner"), cell: (info) => {
cell: (info) => { return renderComponent(CardRunner, { runner: info.getValue() });
return renderComponent(CardRunner, { runner: info.getValue() }); },
}, filterFn: `runner`,
filterFn: `runner`, },
}, {
{ accessorKey: "enabled",
accessorKey: "enabled", cell: (info) => {
cell: (info) => { return renderComponent(CardStatus, { enabled: info.getValue() });
return renderComponent(CardStatus, { enabled: info.getValue() }); },
}, header: () => $_("status"),
header: () => $_("status"), filterFn: `status`,
filterFn: `status`, },
}, {
{ accessorKey: "actions",
accessorKey: "actions", header: () => $_("action"),
header: () => $_("action"), cell: (info) => {
cell: (info) => { return renderComponent(TableActions, {
return renderComponent(TableActions, { detailsAction: () => {
detailsAction: () => { open_edit_modal(
open_edit_modal( current_cards[
current_cards[ current_cards.findIndex((r) => r.id == info.row.original.id)
current_cards.findIndex((r) => r.id == info.row.original.id) ]
] );
); },
}, deleteAction: () => {
deleteAction: () => { active_delete =
active_delete = current_cards[
current_cards[ current_cards.findIndex((r) => r.id == info.row.original.id)
current_cards.findIndex((r) => r.id == info.row.original.id) ];
]; },
}, deleteEnabled:
deleteEnabled: store.state.jwtinfo.userdetails.permissions.includes("CARD:DELETE"),
store.state.jwtinfo.userdetails.permissions.includes("CARD:DELETE"), });
}); },
}, enableColumnFilter: false,
enableColumnFilter: false, enableSorting: false,
enableSorting: false, },
}, ];
];
const options = writable({
const options = writable({ data: [],
data: [], columns: columns,
columns: columns, initialState: {
initialState: { pagination: {
pagination: { pageSize: 50,
pageSize: 50, },
}, },
}, filterFns: {
filterFns: { runner: runnerFilter,
runner: runnerFilter, status: statusFilter,
status: statusFilter, },
}, enableRowSelection: true,
enableRowSelection: true, getCoreRowModel: getCoreRowModel(),
getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(),
getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(),
getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(),
getSortedRowModel: getSortedRowModel(), });
});
const table = createSvelteTable(options);
const table = createSvelteTable(options);
function open_edit_modal(card) {
function open_edit_modal(card) { const getRunnerLabel = (option) =>
const getRunnerLabel = (option) => option.firstname +
option.firstname + " " +
" " + (option.middlename || "") +
(option.middlename || "") + " " +
" " + option.lastname;
option.lastname; if (card.runner?.id) {
if (card.runner?.id) { runner = Object.assign(
runner = Object.assign( { runner },
{ runner }, { label: getRunnerLabel(card.runner), value: card.runner }
{ label: getRunnerLabel(card.runner), value: card.runner } );
); card.runner = card.runner.id;
card.runner = card.runner.id; } else {
} else { card.runner = null;
card.runner = null; runner = null;
runner = null; }
} editable = Object.assign(editable, card);
editable = Object.assign(editable, card); original_data = Object.assign(original_data, card);
original_data = Object.assign(original_data, card); edit_modal_open = true;
edit_modal_open = true; }
}
async function deleteCard(delete_card_id) {
async function deleteCard(delete_card_id) { await RunnerCardService.runnerCardControllerRemove(delete_card_id, true);
await RunnerCardService.runnerCardControllerRemove(delete_card_id, true); current_cards = current_cards.filter((r) => r.id !== delete_card_id);
current_cards = current_cards.filter((r) => r.id !== delete_card_id); options.update((options) => ({
options.update((options) => ({ ...options,
...options, data: current_cards,
data: current_cards, }));
})); toast.success($_("card-deleted"));
Toastify({ }
text: $_("card-deleted"),
duration: 3500, onMount(async () => {
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", toast.loading($_("loading-cards"));
}).showToast(); let page = 0;
} let pagesize = 500;
while (page >= 0) {
onMount(async () => { const cards = await RunnerCardService.runnerCardControllerGetAll(
let page = 0; page,
while (page >= 0) { pagesize
const cards = await RunnerCardService.runnerCardControllerGetAll( );
page, if (cards.length == 0) {
500 page = -2;
); }
if (cards.length == 0) {
page = -2; current_cards = current_cards.concat(...cards);
} options.update((options) => ({
...options,
current_cards = current_cards.concat(...cards); data: current_cards,
options.update((options) => ({ }));
...options,
data: current_cards, dataLoaded = true;
})); page++;
}
dataLoaded = true; toast.dismiss();
page++; toast.success($_("all-cards-loaded"));
} });
console.log("All cards loaded"); </script>
});
</script> {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:UPDATE")}
<CardDetailModal
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:UPDATE")} bind:edit_modal_open
<CardDetailModal bind:runner
bind:edit_modal_open bind:editable
bind:runner bind:original_data
bind:editable on:dataUpdated={(event) => {
bind:original_data current_cards[
on:dataUpdated={(event) => { current_cards.findIndex((c) => c.id === event.detail.card.id)
current_cards[ ] = event.detail.card;
current_cards.findIndex((c) => c.id === event.detail.card.id) current_cards = current_cards;
] = event.detail.card; options.update((options) => ({
current_cards = current_cards; ...options,
options.update((options) => ({ data: current_cards,
...options, }));
data: current_cards, }}
})); />
}} {/if}
/>
{/if} {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")}
<DeleteCardModal
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} delete_card={active_delete}
<DeleteCardModal modal_open={active_delete != undefined}
delete_card={active_delete} on:delete={(event) => {
modal_open={active_delete != undefined} deleteCard(event.detail.id);
on:delete={(event) => { }}
deleteCard(event.detail.id); />
}} {#if !dataLoaded}
/> <div
{#if !dataLoaded} class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
<div role="alert"
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>
<p class="font-bold">{$_("loading-cards")}</p> </div>
<p class="text-sm">{$_("this-might-take-a-moment")}</p> {:else if current_cards.length === 0}
</div> <CardsEmptyState />
{:else if current_cards.length === 0} {:else}
<CardsEmptyState /> <div class="h-12 mt-1">
{:else} {#if selected.length > 0}
<div class="h-12 mt-1"> <button
{#if selected.length > 0} type="button"
<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:w-auto sm:text-sm inline-flex"
type="button" id="options-menu"
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" on:click={async () => {
id="options-menu" const prom = [];
on:click={async () => { for (const card of selectedCards) {
const prom = []; prom.push(
for (const card of selectedCards) { await RunnerCardService.runnerCardControllerRemove(
prom.push( card.id,
await RunnerCardService.runnerCardControllerRemove( true
card.id, )
true );
) }
); await Promise.all(prom);
} for (const card of selectedCards) {
await Promise.all(prom); current_cards = current_cards.filter((r) => r.id !== card.id);
for (const card of selectedCards) { }
current_cards = current_cards.filter((r) => r.id !== card.id); options.update((options) => ({
} ...options,
options.update((options) => ({ data: current_cards,
...options, }));
data: current_cards, $table.resetRowSelection();
})); }}
$table.resetRowSelection(); >
}} {$_("delete-cards")}
> <svg
{$_("delete-cards")} xmlns="http://www.w3.org/2000/svg"
<svg fill="none"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
fill="none" stroke-width="1.5"
viewBox="0 0 24 24" stroke="currentColor"
stroke-width="1.5" class="w-5 h-5"
stroke="currentColor" >
class="w-5 h-5" <path
> stroke-linecap="round"
<path stroke-linejoin="round"
stroke-linecap="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"
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>
</svg> {/if}
</button> <GenerateRunnerCards
{/if} cards_show={selected.length > 0}
<GenerateRunnerCards bind:generate_cards={selectedCards}
cards_show={selected.length > 0} />
bind:generate_cards={selectedCards} </div>
/> <div class="overflow-x-auto">
</div> <table class="w-full">
<div class="overflow-x-auto"> <thead class="border-b border-gray-400">
<table class="w-full"> {#each $table.getHeaderGroups() as headerGroup}
<thead> <tr class="select-none">
{#each $table.getHeaderGroups() as headerGroup} <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<tr class="select-none"> <InputElement
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px"> type="checkbox"
<InputElement checked={$table.getIsAllRowsSelected()}
type="checkbox" indeterminate={$table.getIsSomeRowsSelected()}
checked={$table.getIsAllRowsSelected()} on:change={() => $table.toggleAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()} />
on:change={() => $table.toggleAllRowsSelected()} </th>
/> {#each headerGroup.headers as header}
</th> <TableHeader {header} />
{#each headerGroup.headers as header} {/each}
<TableHeader {header} /> </tr>
{/each} {/each}
</tr> </thead>
{/each} <tbody>
</thead> {#each $table.getRowModel().rows as row}
<tbody> <tr class="odd:bg-white even:bg-gray-100">
{#each $table.getRowModel().rows as row} <td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<tr> <InputElement
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px"> type="checkbox"
<InputElement checked={row.getIsSelected()}
type="checkbox" on:change={() => row.toggleSelected()}
checked={row.getIsSelected()} />
on:change={() => row.toggleSelected()} </td>
/> {#each row.getVisibleCells() as cell}
</td> <td>
{#each row.getVisibleCells() as cell} <svelte:component
<td> this={flexRender(
<svelte:component cell.column.columnDef.cell,
this={flexRender( cell.getContext()
cell.column.columnDef.cell, )}
cell.getContext() />
)} </td>
/> {/each}
</td> </tr>
{/each} {/each}
</tr> </tbody>
{/each} </table>
</tbody> </div>
</table> <TableBottom {table} {selected} />
</div> {/if}
<TableBottom {table} {selected} /> {/if}
{/if}
{/if} <style>
table tbody tr td:nth-child(2) {
font-family: monospace;
}
</style>

View File

@@ -1,57 +0,0 @@
<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

@@ -1,45 +0,0 @@
<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

@@ -1,7 +1,7 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { import {
GroupContactService, GroupContactService,
RunnerTeamService, RunnerTeamService,
@@ -9,7 +9,7 @@
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
import isMobilePhone from "validator/es/lib/isMobilePhone"; import isMobilePhone from "validator/es/lib/isMobilePhone";
import Toastify from "toastify-js"; import toast from "svelte-french-toast";
export let modal_open; export let modal_open;
export let current_contacts; export let current_contacts;
$: selected_team = []; $: selected_team = [];
@@ -85,10 +85,7 @@
function submit() { function submit() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ toast.loading($_("contact-is-being-added"));
text: $_('contact-is-being-added'),
duration: -1,
}).showToast();
let address = {}; let address = {};
if (address_checked === true) { if (address_checked === true) {
address = { address = {
@@ -122,11 +119,8 @@
email_input_value = ""; email_input_value = "";
modal_open = false; modal_open = false;
// //
Toastify({ toast.dismiss();
text: $_('contact-added'), toast.success($_("contact-added"));
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_contacts.push(result); current_contacts.push(result);
current_contacts = current_contacts; current_contacts = current_contacts;
}) })
@@ -135,8 +129,6 @@
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
//
toast.hideToast();
}); });
} }
} }
@@ -145,58 +137,70 @@
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;
}}> }}
>
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" /> data-id="modal_backdrop"
/>
</div> </div>
<span <span
class="hidden sm:inline-block sm:align-middle sm:h-screen" class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span> aria-hidden="true">&#8203;</span
>
<div <div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline"> aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg <svg
class="h-6 w-6 text-blue-600" class="h-6 w-6 text-blue-600"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
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> d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z"
/></svg
>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-contact')} {$_("create-a-new-contact")}
</h3> </h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_('please-provide-the-required-information-to-add-a-new-contact')} {$_(
"please-provide-the-required-information-to-add-a-new-contact"
)}
</p> </p>
</div> </div>
<div class="grid grid-cols-6 gap-6"> <div class="grid grid-cols-6 gap-6">
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="firstname" for="firstname"
class="block text-sm font-medium text-gray-700">{$_('first-name')}</label> class="block text-sm font-medium text-gray-700"
>{$_("first-name")}</label
>
<input <input
use:focus use:focus
autocomplete="off" autocomplete="off"
placeholder={$_('first-name')} placeholder={$_("first-name")}
class:border-red-500={!isFirstnameValid} class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid} class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid} class:focus:ring-red-500={!isFirstnameValid}
@@ -204,34 +208,41 @@
bind:this={firstname_input} bind:this={firstname_input}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isFirstnameValid} {#if !isFirstnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('first-name-is-required')} >
{$_("first-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="trackname" 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 <input
autocomplete="off" autocomplete="off"
placeholder={$_('middle-name')} placeholder={$_("middle-name")}
bind:value={middlename_input_value} bind:value={middlename_input_value}
bind:this={middlename_input} bind:this={middlename_input}
type="text" type="text"
name="trackname" 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>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="lastname" for="lastname"
class="block text-sm font-medium text-gray-700">{$_('last-name')}</label> class="block text-sm font-medium text-gray-700"
>{$_("last-name")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder="{$_('last-name')}" placeholder={$_("last-name")}
class:border-red-500={!isLastnameValid} class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid} class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid} class:focus:ring-red-500={!isLastnameValid}
@@ -239,23 +250,28 @@
bind:this={lastname_input} bind:this={lastname_input}
type="text" type="text"
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 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} {#if !isLastnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('last-name-is-required')} >
{$_("last-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="team" for="team"
class="block text-sm font-medium text-gray-700">{$_('teams')}</label> class="block text-sm font-medium text-gray-700"
>{$_("teams")}</label
>
<select <select
name="team" name="team"
multiple multiple
bind:value={selected_team} bind:value={selected_team}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
>
{#each teams as team} {#each teams as team}
<option value={team.id}> <option value={team.id}>
{team.parentGroup.name} {team.parentGroup.name}
@@ -271,10 +287,12 @@
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="phone" for="phone"
class="block text-sm font-medium text-gray-700">{$_('phone')}</label> class="block text-sm font-medium text-gray-700"
>{$_("phone")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('phone')} placeholder={$_("phone")}
class:border-red-500={!isPhoneValidOrEmpty} class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty} class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty} class:focus:ring-red-500={!isPhoneValidOrEmpty}
@@ -282,21 +300,27 @@
bind:this={phone_input} bind:this={phone_input}
type="tel" type="tel"
name="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" /> 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} {#if !isPhoneValidOrEmpty}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')} >
{@html $_(
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number"
)}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="email" 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 <input
autocomplete="off" autocomplete="off"
placeholder={$_('e-mail-adress')} placeholder={$_("e-mail-adress")}
class:border-red-500={!isEmailValidOrEmpty} class:border-red-500={!isEmailValidOrEmpty}
class:focus:border-red-500={!isEmailValidOrEmpty} class:focus:border-red-500={!isEmailValidOrEmpty}
class:focus:ring-red-500={!isEmailValidOrEmpty} class:focus:ring-red-500={!isEmailValidOrEmpty}
@@ -304,11 +328,13 @@
bind:this={email_input} bind:this={email_input}
type="email" type="email"
name="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} {#if !isEmailValidOrEmpty}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-email-is-required')} >
{$_("valid-email-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
@@ -319,22 +345,25 @@
id="comments" id="comments"
name="comments" name="comments"
type="checkbox" type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label <label for="comments" class="font-medium text-gray-700"
for="comments" >{$_("address")}</label
class="font-medium text-gray-700">{$_('address')}</label> >
</div> </div>
</div> </div>
{#if address_checked === true} {#if address_checked === true}
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="address1" 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 <input
autocomplete="off" autocomplete="off"
placeholder="{$_('address')}" placeholder={$_("address")}
class:border-red-500={!isAddress1Valid} class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid} class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid} class:focus:ring-red-500={!isAddress1Valid}
@@ -342,34 +371,41 @@
bind:this={address_input1} bind:this={address_input1}
type="text" type="text"
name="address1" 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} {#if !isAddress1Valid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('address-is-required')} >
{$_("address-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="address2" 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 <input
autocomplete="off" autocomplete="off"
placeholder={$_('apartment-suite-etc')} placeholder={$_("apartment-suite-etc")}
bind:value={address_input2_value} bind:value={address_input2_value}
bind:this={address_input2} bind:this={address_input2}
type="text" type="text"
name="address2" 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>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="zipcode" 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 <input
autocomplete="off" autocomplete="off"
placeholder={$_('zip-postal-code')} placeholder={$_("zip-postal-code")}
class:border-red-500={!iszipcodevalid} class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid} class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid} class:focus:ring-red-500={!iszipcodevalid}
@@ -377,21 +413,25 @@
bind:this={address_zipcode} bind:this={address_zipcode}
type="text" type="text"
name="zipcode" 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} {#if !iszipcodevalid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-zipcode-postal-code-is-required')} >
{$_("valid-zipcode-postal-code-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="city" 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 <input
autocomplete="off" autocomplete="off"
placeholder="{$_('city')}" placeholder={$_("city")}
class:border-red-500={!iscityvalid} class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid} class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid} class:focus:ring-red-500={!iscityvalid}
@@ -399,11 +439,13 @@
bind:this={address_city} bind:this={address_city}
type="text" type="text"
name="city" 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} {#if !iscityvalid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-city-is-required')} >
{$_("valid-city-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
@@ -418,16 +460,18 @@
class:opacity-50={!createbtnenabled} class:opacity-50={!createbtnenabled}
on:click={submit} on:click={submit}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('create')} >
{$_("create")}
</button> </button>
<button <button
on:click={() => { on:click={() => {
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
{$_('cancel')} >
{$_("cancel")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -6,9 +6,9 @@
RunnerTeamService, RunnerTeamService,
RunnerOrganizationService, RunnerOrganizationService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
import toast from "svelte-french-toast";
let data_loaded = false; let data_loaded = false;
let orgs = []; let orgs = [];
let teams = []; let teams = [];
@@ -31,7 +31,7 @@
isEmailValid && isEmailValid &&
isPhoneValidOrEmpty && isPhoneValidOrEmpty &&
((isAddress1Valid && iszipcodevalid && iscityvalid) || ((isAddress1Valid && iszipcodevalid && iscityvalid) ||
editable.address_checked === false); editable.address_checked === false);
const promise = GroupContactService.groupContactControllerGetOne( const promise = GroupContactService.groupContactControllerGetOne(
params.contact params.contact
).then((data) => { ).then((data) => {
@@ -42,14 +42,14 @@
original_data.groups = original_data.groups.map((g) => g.id); original_data.groups = original_data.groups.map((g) => g.id);
editable.address_checked = editable.address.address1 !== null; editable.address_checked = editable.address.address1 !== null;
original_data.address_checked = editable.address.address1 !== null; original_data.address_checked = editable.address.address1 !== null;
if(editable.address_checked===false){ if (editable.address_checked === false) {
editable.address = { editable.address = {
address1: "", address1: "",
address2: "", address2: "",
city: "", city: "",
postalcode: "", postalcode: "",
country: "" country: "",
} };
} }
}); });
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
@@ -67,10 +67,7 @@
$: iscityvalid = editable.address?.city?.trim().length !== 0; $: iscityvalid = editable.address?.city?.trim().length !== 0;
function submit() { function submit() {
if (data_loaded === true && save_enabled) { if (data_loaded === true && save_enabled) {
Toastify({ toast.loading($_("contact-is-being-updated"));
text: $_("contact-is-being-updated"),
duration: 2500,
}).showToast();
editable.address.country = "DE"; editable.address.country = "DE";
if (editable.address_checked === false) { if (editable.address_checked === false) {
editable.address = null; editable.address = null;
@@ -81,12 +78,9 @@
GroupContactService.groupContactControllerPut(original_data.id, editable) GroupContactService.groupContactControllerPut(original_data.id, editable)
.then((resp) => { .then((resp) => {
Object.assign(original_data, editable); Object.assign(original_data, editable);
original_data=original_data; original_data = original_data;
Toastify({ toast.dismiss();
text: $_("updated-contact"), toast.success($_("updated-contact"));
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}) })
.catch((err) => {}); .catch((err) => {});
} else { } else {
@@ -102,7 +96,7 @@
</script> </script>
{#await promise} {#await promise}
{$_('loading-contact-details')} {$_("loading-contact-details")}
{:then} {:then}
<section class="container p-5 select-none"> <section class="container p-5 select-none">
<div class="flex flex-row mb-4"> <div class="flex flex-row mb-4">
@@ -115,12 +109,15 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
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> d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z"
/></svg
>
</li> </li>
<li class="flex items-center ml-2"> <li class="flex items-center ml-2">
<a class="mr-2" href="./">{$_('contacts')}</a><svg <a class="mr-2" href="./">{$_("contacts")}</a><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
@@ -130,17 +127,17 @@
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"
x1="5" ><line x1="5" y1="12" x2="19" y2="12" />
y1="12" <polyline points="12 5 19 12 12 19" /></svg
x2="19" >
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="mr-2">{original_data.firstname} <span class="mr-2"
{original_data.middlename || ''} >{original_data.firstname}
{original_data.lastname}</span> {original_data.middlename || ""}
{original_data.lastname}</span
>
</li> </li>
</ol> </ol>
</nav> </nav>
@@ -148,19 +145,23 @@
</div> </div>
<div class="mb-8 text-3xl font-extrabold leading-tight"> <div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.firstname} {original_data.firstname}
{original_data.middlename || ''} {original_data.middlename || ""}
{original_data.lastname} {original_data.lastname}
<span data-id="contact_actions_${editable.id}"> <span data-id="contact_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:DELETE")}
{#if delete_triggered} {#if delete_triggered}
<button <button
on:click={deleteContact} on:click={deleteContact}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button> 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:w-auto sm:text-sm"
>{$_("confirm-deletion")}</button
>
<button <button
on:click={() => { on:click={() => {
delete_triggered = !delete_triggered; 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}
{#if !delete_triggered} {#if !delete_triggered}
<button <button
@@ -168,7 +169,9 @@
delete_triggered = true; delete_triggered = true;
}} }}
type="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">{$_('delete-contact')}</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:w-auto sm:text-sm"
>{$_("delete-contact")}</button
>
{/if} {/if}
{/if} {/if}
{#if !delete_triggered} {#if !delete_triggered}
@@ -177,112 +180,124 @@
class:opacity-50={!save_enabled} class:opacity-50={!save_enabled}
type="button" type="button"
on:click={submit} 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> 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:w-auto sm:text-sm"
>{$_("save-changes")}</button
>
{/if} {/if}
</span> </span>
</div> </div>
<!-- --> <!-- -->
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label for="firstname" class="font-medium text-gray-700"
for="firstname" >{$_("first-name")}</label
class="font-medium text-gray-700">{$_('first-name')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('first-name')} placeholder={$_("first-name")}
type="text" type="text"
class:border-red-500={!isFirstnameValid} class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid} class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid} class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname} bind:value={editable.firstname}
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isFirstnameValid} {#if !isFirstnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('first-name-is-required')} >
{$_("first-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label for="middlename" class="font-medium text-gray-700"
for="middlename" >{$_("middle-name")}</label
class="font-medium text-gray-700">{$_('middle-name')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('middle-name')} placeholder={$_("middle-name")}
type="text" type="text"
bind:value={editable.middlename} bind:value={editable.middlename}
name="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" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label for="lastname" class="font-medium text-gray-700"
for="lastname" >{$_("last-name")}</label
class="font-medium text-gray-700">{$_('last-name')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('last-name')} placeholder={$_("last-name")}
type="text" type="text"
bind:value={editable.lastname} bind:value={editable.lastname}
class:border-red-500={!isLastnameValid} class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid} class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid} class:focus:ring-red-500={!isLastnameValid}
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 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} {#if !isLastnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('last-name-is-required')} >
{$_("last-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label for="email" class="font-medium text-gray-700"
for="email" >{$_("e-mail-adress")}</label
class="font-medium text-gray-700">{$_('e-mail-adress')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('e-mail-adress')} placeholder={$_("e-mail-adress")}
type="email" type="email"
bind:value={editable.email} bind:value={editable.email}
class:border-red-500={!isEmailValid} class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid} class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid} class:focus:ring-red-500={!isEmailValid}
name="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 !isEmailValid} {#if !isEmailValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-email-is-required')} >
{$_("valid-email-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label for="phone" class="font-medium text-gray-700">{$_('phone')}</label> <label for="phone" class="font-medium text-gray-700">{$_("phone")}</label>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('phone')} placeholder={$_("phone")}
type="tel" type="tel"
class:border-red-500={!isPhoneValidOrEmpty} class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty} class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty} class:focus:ring-red-500={!isPhoneValidOrEmpty}
bind:value={editable.phone} bind:value={editable.phone}
name="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" /> 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} {#if !isPhoneValidOrEmpty}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-international-phone-number-is-required')} >
{$_("valid-international-phone-number-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('groups')}</span> <span class="font-medium text-gray-700">{$_("groups")}</span>
<select <select
bind:value={editable.groups} bind:value={editable.groups}
name="team" name="team"
multiple multiple
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
>
{#each teams as team} {#each teams as team}
<option value={team.id}> <option value={team.id}>
{team.parentGroup.name} {team.parentGroup.name}
@@ -303,19 +318,20 @@
id="comments" id="comments"
name="comments" name="comments"
type="checkbox" type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label <label for="comments" class="font-medium text-gray-700"
for="comments" >{$_("address")}</label
class="font-medium text-gray-700">{$_('address')}</label> >
</div> </div>
</div> </div>
{#if editable.address_checked === true} {#if editable.address_checked === true}
<div class="col-span-6"> <div class="col-span-6">
<label <label for="address1" class="block text-sm font-medium text-gray-700"
for="address1" >{$_("address")}</label
class="block text-sm font-medium text-gray-700">{$_('address')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder="Address" placeholder="Address"
@@ -325,65 +341,72 @@
bind:value={editable.address.address1} bind:value={editable.address.address1}
type="text" type="text"
name="address1" 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} {#if !isAddress1Valid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('address-is-required')} >
{$_("address-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label for="address2" class="block text-sm font-medium text-gray-700"
for="address2" >{$_("apartment-suite-etc")}</label
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('apartment-suite-etc')} placeholder={$_("apartment-suite-etc")}
bind:value={editable.address.address2} bind:value={editable.address.address2}
type="text" type="text"
name="address2" 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>
<div class="col-span-6"> <div class="col-span-6">
<label <label for="zipcode" class="block text-sm font-medium text-gray-700"
for="zipcode" >{$_("zip-postal-code")}</label
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('zip-postal-code')} placeholder={$_("zip-postal-code")}
class:border-red-500={!iszipcodevalid} class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid} class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid} class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode} bind:value={editable.address.postalcode}
type="text" type="text"
name="zipcode" 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} {#if !iszipcodevalid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-zipcode-postal-code-is-required')} >
{$_("valid-zipcode-postal-code-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label for="city" class="block text-sm font-medium text-gray-700"
for="city" >{$_("city")}</label
class="block text-sm font-medium text-gray-700">{$_('city')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('city')} placeholder={$_("city")}
class:border-red-500={!iscityvalid} class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid} class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid} class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city} bind:value={editable.address.city}
type="text" type="text"
name="city" 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} {#if !iscityvalid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-city-is-required')} >
{$_("valid-city-is-required")}
</span> </span>
{/if} {/if}
</div> </div>

View File

@@ -8,22 +8,23 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_('contacts')} {$_("contacts")}
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:CREATE')} </h4>
<button {#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:CREATE")}
on:click={() => { <button
modal_open = true; 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"> type="button"
{$_('create-a-new-contact')} 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:w-auto sm:text-sm"
</button> >
{/if} {$_("create-a-new-contact")}
</span> </button>
{/if}
<ContactsOverview bind:current_contacts /> <ContactsOverview bind:current_contacts />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:CREATE")}
<AddContactModal bind:current_contacts bind:modal_open /> <AddContactModal bind:current_contacts bind:modal_open />
{/if} {/if}

View File

@@ -9,8 +9,8 @@
<div class="text-center items-center justify-center"> <div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500"> <p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={team_empty} alt="" /> <img class="w-full h-44" src={team_empty} alt="" />
<span class="font-bold">{$_('there-are-no-contacts-added-yet')}</span><br /> <span class="font-bold">{$_("there-are-no-contacts-added-yet")}</span><br />
<span>{$_('add-your-first-contact')}</span> <span>{$_("add-your-first-contact")}</span>
</p> </p>
</div> </div>

View File

@@ -1,6 +1,5 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import { GroupContactService } from "@odit/lfk-client-js"; import { GroupContactService } from "@odit/lfk-client-js";
const promise = GroupContactService.groupContactControllerGetAll().then( const promise = GroupContactService.groupContactControllerGetAll().then(
(result) => { (result) => {
@@ -9,18 +8,20 @@
); );
import store from "../../store"; import store from "../../store";
import ContactsEmptyState from "./ContactsEmptyState.svelte"; import ContactsEmptyState from "./ContactsEmptyState.svelte";
import toast from "svelte-french-toast";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
export let current_contacts = []; export let current_contacts = [];
</script> </script>
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:GET")}
{#await promise} {#await promise}
<div <div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"> role="alert"
<p class="font-bold">{$_('contacts-are-being-loaded')}</p> >
<p class="text-sm">{$_('this-might-take-a-moment')}</p> <p class="font-bold">{$_("contacts-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:then} {:then}
{#if current_contacts.length === 0} {#if current_contacts.length === 0}
@@ -29,31 +30,36 @@
<input <input
type="search" type="search"
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_('datatable.search')} placeholder={$_("datatable.search")}
aria-label={$_('datatable.search')} aria-label={$_("datatable.search")}
class="mb-4" /> class="mb-2 w-full sm:w-auto mt-1 sm:mt-0 p-2 rounded-md border"
/>
<div <div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<table class="divide-y divide-gray-200 w-full"> <table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50"> <thead class="bg-gray-50">
<tr> <tr class="odd:bg-white even:bg-gray-100">
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('name')} >
{$_("name")}
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('groups')} >
{$_("groups")}
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('address')} >
{$_("address")}
</th> </th>
<th scope="col" class="relative px-6 py-3"> <th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span> <span class="sr-only">{$_("action")}</span>
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -63,13 +69,16 @@
.toString() .toString()
.toLowerCase() .toLowerCase()
.includes(searchvalue)} .includes(searchvalue)}
<tr data-rowid="team_{t.id}"> <tr
class="odd:bg-white even:bg-gray-100"
data-rowid="team_{t.id}"
>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center"> <div class="flex items-center">
<div class="ml-4"> <div class="ml-4">
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{t.firstname} {t.firstname}
{t.middlename || ''} {t.middlename || ""}
{t.lastname} {t.lastname}
</div> </div>
</div> </div>
@@ -81,20 +90,24 @@
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{#if t.groups.length > 0} {#if t.groups.length > 0}
{#each t.groups as g} {#each t.groups as g}
{#if g.responseType === 'RUNNERORGANIZATION'} {#if g.responseType === "RUNNERORGANIZATION"}
<a <a
href="../orgs/{g.id}" href="../orgs/{g.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{g.name}</a> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{g.name}</a
>
{:else} {:else}
<a <a
href="../teams/{g.id}" href="../teams/{g.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{g.parentGroup.name} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{g.parentGroup.name}
&gt; &gt;
{g.name}</a> {g.name}</a
>
{/if} {/if}
{/each} {/each}
{:else} {:else}
{$_('contact-is-not-a-member-in-any-group')} {$_("contact-is-not-a-member-in-any-group")}
{/if} {/if}
</div> </div>
</div> </div>
@@ -106,7 +119,7 @@
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{#if t.address.address1 !== null} {#if t.address.address1 !== null}
{t.address.address1}<br /> {t.address.address1}<br />
{t.address.address2 || ''}<br /> {t.address.address2 || ""}<br />
{t.address.postalcode} {t.address.postalcode}
{t.address.city} {t.address.city}
{t.address.country} {t.address.country}
@@ -117,45 +130,53 @@
</td> </td>
{#if active_deletes[t.id] === true} {#if active_deletes[t.id] === true}
<td <td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button <button
on:click={() => { on:click={() => {
active_deletes[t.id] = false; active_deletes[t.id] = false;
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button <button
on:click={() => { on:click={() => {
GroupContactService.groupContactControllerRemove(t.id, false).then( toast.loading($_("deleting-contact"));
(resp) => { GroupContactService.groupContactControllerRemove(
current_contacts = current_contacts.filter( t.id,
(obj) => obj.id !== t.id false
); ).then((resp) => {
Toastify({ current_contacts = current_contacts.filter(
text: $_('contact-deleted'), (obj) => obj.id !== t.id
duration: 500, );
backgroundColor: toast.dismiss();
'linear-gradient(to right, #00b09b, #96c93d)', toast($_("contact-deleted"));
}).showToast(); });
}
);
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
</td> </td>
{:else} {:else}
<td <td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a <a
href="./{t.id}" href="./{t.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> class="text-indigo-600 hover:text-indigo-900"
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')} >{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:DELETE")}
<button <button
on:click={() => { on:click={() => {
active_deletes[t.id] = true; active_deletes[t.id] = true;
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if} {/if}
</td> </td>
{/if} {/if}
@@ -169,7 +190,7 @@
{:catch error} {:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> <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"> <span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b> <b class="capitalize">{$_("general_promise_error")}</b>
{error} {error}
</span> </span>
</div> </div>

View File

@@ -1,361 +1,428 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import localForage from "localforage"; import localForage from "localforage";
import store from "../../store"; import store from "../../store";
import { router } from "tinro"; import { router } from "tinro";
import NoComponentLoaded from "../base/NoComponentLoaded.svelte"; import NoComponentLoaded from "../base/NoComponentLoaded.svelte";
import { AuthService } from "@odit/lfk-client-js"; import { AuthService } from "@odit/lfk-client-js";
$: navOpen = false; import { Toaster } from "svelte-french-toast";
function logout() { $: navOpen = false;
localForage.clear(); function logout() {
location.replace("/"); localForage.clear();
} location.replace("/");
</script> }
</script>
<style>
.collapsed_navigation { <section class="min-h-screen bg-gray-50">
transform: translateX(-100%); <div
} class:collapsed_navigation={!navOpen}
@media (min-width: 768px) { 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"
.collapsed_navigation { >
transform: translateX(0px); <a href="/" class="flex items-center px-4 py-5">
} <img src="/lfk-logo.png" alt="Logo" class="h-10" />
} <h3 class="text-lg font-bold">LfK!Admin</h3>
</style> </a>
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation">
<section class="min-h-screen bg-gray-50"> <a
<div class:activenav={$router.path === "/"}
class:collapsed_navigation={!navOpen} class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
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"> href="/"
<a href="/" class="flex items-center px-4 py-5"> >
<img src="/lfk-logo.png" alt="Logo" class="h-10" /> <svg
<h3 class="text-lg">Lauf für Kaya! Admin</h3> class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
</a> xmlns="http://www.w3.org/2000/svg"
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation"> viewBox="0 0 20 20"
<a fill="currentColor"
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" <path
href="/"> 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 />
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" </svg>
xmlns="http://www.w3.org/2000/svg" <span>{$_("dashboard-title")}</span>
viewBox="0 0 20 20" </a>
fill="currentColor"> {#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:GET")}
<path <a
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" /> class:activenav={$router.path.includes("/orgs/")}
</svg> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
<span>{$_('dashboard-title')}</span> href="/orgs/"
</a> >
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')} <svg
<a class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
class:bg-gray-100={$router.path.includes('/orgs/')} fill="currentColor"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" xmlns="http://www.w3.org/2000/svg"
href="/orgs/"> viewBox="0 0 24 24"
<svg width="24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" height="24"
fill="currentColor" ><path fill="none" d="M0 0h24v24H0z" />
xmlns="http://www.w3.org/2000/svg" <path
viewBox="0 0 24 24" 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"
width="24" /></svg
height="24"><path fill="none" d="M0 0h24v24H0z" /> >
<path <span>{$_("orgs")}</span>
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> </a>
<span>{$_('orgs')}</span> {/if}
</a> {#if store.state.jwtinfo.userdetails.permissions.includes("USER:GET")}
{/if} <a
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')} class:activenav={$router.path === "/users/"}
<a class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
class:bg-gray-100={$router.path === '/users/'} href="/users/"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" >
href="/users/"> <svg
<svg xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" width="24"
width="24" height="24"
height="24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" fill="currentColor"
fill="currentColor" viewBox="0 0 24 24"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> ><path fill="none" d="M0 0h24v24H0z" />
<path <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> 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"
<span>{$_('users')}</span> /></svg
</a> >
{/if} <span>{$_("users")}</span>
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')} </a>
<a {/if}
class:bg-gray-100={$router.path === '/groups/'} {#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:GET")}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" <a
href="/groups/"> class:activenav={$router.path === "/groups/"}
<svg class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" href="/groups/"
fill="currentColor" >
width="24" <svg
height="24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 640 512"><path width="24"
fill="currentColor" height="24"
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> xmlns="http://www.w3.org/2000/svg"
<span>{$_('user-groups')}</span> viewBox="0 0 640 512"
</a> ><path
{/if} fill="currentColor"
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} 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"
<a /></svg
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" <span>{$_("user-groups")}</span>
href="/runners/"> </a>
<svg {/if}
xmlns="http://www.w3.org/2000/svg" {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")}
viewBox="0 0 24 24" <a
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class:activenav={$router.path === "/runners/"}
fill="currentColor" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
width="24" href="/runners/"
height="24"><path fill="none" d="M0 0h24v24H0z" /> >
<path <svg
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg> xmlns="http://www.w3.org/2000/svg"
<span>{$_('runners')}</span> viewBox="0 0 24 24"
</a> class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
{/if} fill="currentColor"
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')} width="24"
<a height="24"
class:bg-gray-100={$router.path === '/teams/'} ><path fill="none" d="M0 0h24v24H0z" />
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" <path
href="/teams/"> 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 /></svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" >
fill="currentColor" <span>{$_("runners")}</span>
width="24" </a>
height="24" {/if}
xmlns="http://www.w3.org/2000/svg" {#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:GET")}
viewBox="0 0 640 512"><path <a
fill="currentColor" class:activenav={$router.path === "/teams/"}
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
<span>{$_('teams')}</span> href="/teams/"
</a> >
{/if} <svg
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')} class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
<a fill="currentColor"
class:bg-gray-100={$router.path.includes('/donors/')} width="24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" height="24"
href="/donors/"> xmlns="http://www.w3.org/2000/svg"
<svg viewBox="0 0 640 512"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" ><path
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" 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"
viewBox="0 0 24 24" /></svg
width="24" >
height="24"><path fill="none" d="M0 0h24v24H0z" /> <span>{$_("teams")}</span>
<path </a>
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> {/if}
<span>{$_('donors')}</span> {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")}
</a> <a
{/if} class:activenav={$router.path.includes("/donors/")}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')} class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
<a href="/donors/"
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" <svg
href="/donations/"> class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
<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"
fill="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" width="24"
viewBox="0 0 24 24" height="24"
width="24" ><path fill="none" d="M0 0h24v24H0z" />
height="24"><path fill="none" d="M0 0h24v24H0z" /> <path
<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"
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> /></svg
<span>{$_('donations')}</span> >
</a> <span>{$_("donors")}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')} {/if}
<a {#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:GET")}
class:bg-gray-100={$router.path === '/tracks/'} <a
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class:activenav={$router.path.includes("/donations/")}
href="/tracks/"> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
<svg href="/donations/"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" >
fill="currentColor" <svg
width="24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
height="24" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path viewBox="0 0 24 24"
fill="currentColor" width="24"
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> height="24"
<span>{$_('tracks')}</span> ><path fill="none" d="M0 0h24v24H0z" />
</a> <path
{/if} 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"
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')} /></svg
<a >
class:bg-gray-100={$router.path === '/cards/'} <span>{$_("donations")}</span>
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" </a>
href="/cards/"> {/if}
<svg {#if store.state.jwtinfo.userdetails.permissions.includes("TRACK:GET")}
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" <a
fill="currentColor" class:activenav={$router.path === "/tracks/"}
width="24" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
height="24" href="/tracks/"
xmlns="http://www.w3.org/2000/svg" >
viewBox="0 0 24 24"> <svg
<path fill="none" d="M0 0h24v24H0z" /> class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
<path fill="currentColor"
fill="currentColor" width="24"
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> height="24"
<span>{$_('cards')}</span> xmlns="http://www.w3.org/2000/svg"
</a> viewBox="0 0 640 512"
{/if} ><path
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')} fill="currentColor"
<a 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"
class:bg-gray-100={$router.path === '/scans/'} /></svg
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" >
href="/scans/"> <span>{$_("tracks")}</span>
<svg </a>
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" {/if}
fill="currentColor" {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")}
width="24" <a
height="24" class:activenav={$router.path === "/cards/"}
xmlns="http://www.w3.org/2000/svg" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> href="/cards/"
<path >
fill="currentColor" <svg
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
<span>Scans</span> fill="currentColor"
</a> width="24"
{/if} height="24"
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')} xmlns="http://www.w3.org/2000/svg"
<a viewBox="0 0 24 24"
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" <path fill="none" d="M0 0h24v24H0z" />
href="/contacts/"> <path
<svg fill="currentColor"
fill="currentColor" d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" /></svg
xmlns="http://www.w3.org/2000/svg" >
viewBox="0 0 24 24" <span>{$_("cards")}</span>
width="24" </a>
height="24"><path fill="none" d="M0 0h24v24H0z" /> {/if}
<path {#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")}
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> <a
<span>{$_('contacts')}</span> class:activenav={$router.path === "/scans/"}
</a> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
{/if} href="/scans/"
{#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')} >
<a <svg
class:bg-gray-100={$router.path === '/scanstations/'} class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" fill="currentColor"
href="/scanstations/"> width="24"
<svg height="24"
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="currentColor" viewBox="0 0 24 24"
width="24" ><path fill="none" d="M0 0h24v24H0z" />
height="24" <path
viewBox="0 0 24 24" fill="currentColor"
xmlns="http://www.w3.org/2000/svg"><path d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z"
fill="none" /></svg
d="M0 0h24v24H0z" /> >
<path <span>Scans</span>
fill="currentColor" </a>
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> {/if}
<span>{$_('scanstations')}</span> {#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:GET")}
</a> <a
{/if} class:activenav={$router.path === "/contacts/"}
{#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:GET')} class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
<a href="/contacts/"
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" <svg
href="/statsclients/"> fill="currentColor"
<svg class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" xmlns="http://www.w3.org/2000/svg"
fill="currentColor" viewBox="0 0 24 24"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" ><path fill="none" d="M0 0h24v24H0z" />
xmlns="http://www.w3.org/2000/svg"><path <path
fill="none" 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"
d="M0 0h24v24H0z" /> /></svg
<path >
fill="currentColor" <span>{$_("contacts")}</span>
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> </a>
<span>{$_('statsclients')}</span> {/if}
</a> {#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")}
{/if} <a
<a class:activenav={$router.path === "/scanstations/"}
class:bg-gray-100={$router.path === '/settings/'} class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" href="/scanstations/"
href="/settings/"> >
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 20 20" width="24"
fill="currentColor"> height="24"
<path viewBox="0 0 24 24"
fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg"
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" ><path fill="none" d="M0 0h24v24H0z" />
clip-rule="evenodd" /> <path
</svg> fill="currentColor"
<span>{$_('settings')}</span> d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
</a> /></svg
<a >
class:bg-gray-100={$router.path === '/about/'} <span>{$_("scanstations")}</span>
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" </a>
href="/about/"> {/if}
<svg {#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:GET")}
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" <a
xmlns="http://www.w3.org/2000/svg" class:activenav={$router.path === "/statsclients/"}
fill="none" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
stroke="currentColor" href="/statsclients/"
stroke-width="2" >
stroke-linecap="round" <svg
stroke-linejoin="round" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" /> fill="currentColor"
<path d="M12 16v-4M12 8h.01" /></svg> width="24"
<span>{$_('about')}</span> height="24"
</a> viewBox="0 0 24 24"
<span xmlns="http://www.w3.org/2000/svg"
tabindex="0" ><path fill="none" d="M0 0h24v24H0z" />
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" <path
on:click={() => { fill="currentColor"
AuthService.authControllerLogout(); d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
logout(); /></svg
}}> >
<svg <span>{$_("statsclients")}</span>
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" </a>
fill="currentColor" {/if}
width="24" <a
height="24" class:activenav={$router.path === "/settings/"}
xmlns="http://www.w3.org/2000/svg" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> href="/settings/"
<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> <svg
<span>{$_('logout')}</span> class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
</span> xmlns="http://www.w3.org/2000/svg"
</nav> viewBox="0 0 20 20"
</div> fill="currentColor"
<div class="ml-0 transition md:ml-60"> >
<header <path
class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden"> fill-rule="evenodd"
<button 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"
on:click={() => { clip-rule="evenodd"
navOpen = true; />
}} </svg>
class="block btn btn-light md:hidden"> <span>{$_("settings")}</span>
<span class="sr-only">Menu</span><svg </a>
class="w-4 h-4" <a
xmlns="http://www.w3.org/2000/svg" class:activenav={$router.path === "/about/"}
viewBox="0 0 20 20" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
fill="currentcolor"><path href="/about/"
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" <svg
clip-rule="evenodd" /></svg></button> class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
</header> xmlns="http://www.w3.org/2000/svg"
<slot> fill="none"
<NoComponentLoaded /> stroke="currentColor"
</slot> stroke-width="2"
</div> stroke-linecap="round"
{#if navOpen === true} stroke-linejoin="round"
<div viewBox="0 0 24 24"
on:click={() => { ><circle cx="12" cy="12" r="10" />
navOpen = false; <path d="M12 16v-4M12 8h.01" /></svg
console.log({ navOpen }); >
}} <span>{$_("about")}</span>
class:hidden={!navOpen} </a>
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" /> <button
{/if} tabindex="0"
</section> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900"
on:click={() => {
AuthService.authControllerLogout();
logout();
}}
>
<svg
class="flex-shrink-0 w-5 h-5 mr-2 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>
</button>
</nav>
</div>
<div class="ml-0 transition md:ml-60">
<header
class="flex items-center 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 xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
</button
>
<span class="inline-block">
<img src="/lfk-logo.png" alt="Logo" class="h-8 inline-block" />
<span class="text-lg font-bold">LfK!Admin</span>
</span>
</header>
<Toaster position="top-right" />
<slot>
<NoComponentLoaded />
</slot>
</div>
{#if navOpen === true}
<button
on:click={() => {
navOpen = false;
}}
class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden"
/>
{/if}
</section>
<style>
.collapsed_navigation {
transform: translateX(-100%);
}
@media (min-width: 768px) {
.collapsed_navigation {
transform: translateX(0px);
}
}
</style>

View File

@@ -1,31 +1,18 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { StatsService } from "@odit/lfk-client-js"; import { StatsService } from "@odit/lfk-client-js";
import StatCards from "./StatCard.svelte";
import store from "../../store"; import store from "../../store";
import StatCard from "./StatCard.svelte"; import StatCard from "./StatCard.svelte";
let navOpen = false;
const stats_promise = StatsService.statsControllerGet(); const stats_promise = StatsService.statsControllerGet();
</script> </script>
<div <div class="p-2 md:p-5 overflow-x-hidden">
class="p-5 overflow-x-hidden" <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
on:click={() => { {$_("dashboard-greeting")} <span class="text-blue-500"
navOpen = false; >{store.state.jwtinfo.userdetails.firstname}
}} {store.state.jwtinfo.userdetails.lastname}</span
>
<h1 class="text-3xl leading-tight">
<span class="font-extrabold">{$_("dashboard-title")}</span>
<span>
-
{$_("dashboard-greeting")},
<span class="text-blue-500"
>{store.state.jwtinfo.userdetails.firstname}
{store.state.jwtinfo.userdetails.lastname}</span
></span
> >
</h1> </h4>
<h1>{$_("general-stats")}</h1>
{#await stats_promise} {#await stats_promise}
<div <div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
@@ -36,7 +23,7 @@
</div> </div>
{:then stats} {:then stats}
<div <div
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 2xl:grid-cols-6 gap-4" class="grid gap-1 grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 2xl:grid-cols-6 sm:gap-4"
> >
<StatCard <StatCard
title={$_("runners")} title={$_("runners")}
@@ -109,24 +96,35 @@
</StatCard> </StatCard>
<StatCard <StatCard
title={$_("average-donation")} title={$_("average-donation")}
value={`${(stats.average_donation / 100).toFixed(2)} €`} value={`${parseFloat(stats.average_donation / 100).toLocaleString(
undefined,
{
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
)}`}
href="/donations/" href="/donations/"
> >
<svg <svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24" height="24"
><path fill="none" d="M0 0h24v24H0z" /> fill="currentColor"
width="24"
><path d="M0 0h24v24H0z" fill="none" />
<path <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" 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 /></svg
> >
</StatCard> </StatCard>
<StatCard <StatCard
title={$_("total-donations")} title={$_("total-donations")}
value={`${(stats.total_donation / 100).toFixed(2)} €`} value={`${parseFloat(stats.total_donation / 100).toLocaleString(
undefined,
{
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
)}`}
href="/donations/" href="/donations/"
> >
<svg <svg
@@ -142,8 +140,8 @@
</StatCard> </StatCard>
<StatCard <StatCard
title={$_("total-distance")} title={$_("total-distance")}
value={`${stats.total_distance / 1000} km`} value={`${stats.total_distance / 1000}km`}
href="#" href="/scans/"
> >
<svg <svg
fill="currentColor" fill="currentColor"
@@ -158,8 +156,14 @@
</StatCard> </StatCard>
<StatCard <StatCard
title={$_("average-distance")} title={$_("average-distance")}
value={`${(stats.average_distance / 1000).toFixed(2)} km`} value={`${parseFloat(stats.average_distance / 1000).toLocaleString(
href="#" undefined,
{
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
)}km`}
href="/scans/"
> >
<svg <svg
fill="currentColor" fill="currentColor"

View File

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

View File

@@ -8,9 +8,8 @@
RunnerService, RunnerService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Select from "svelte-select"; import Select from "svelte-select";
import Toastify from "toastify-js"; import { createEventDispatcher, onMount } from "svelte";
import { is_promise } from "svelte/internal"; import toast from "svelte-french-toast";
import { createEventDispatcher } from "svelte";
export let modal_open; export let modal_open;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const getDonorLabel = (option) => const getDonorLabel = (option) =>
@@ -18,25 +17,12 @@
const filterDonors = (label, filterText, option) => const filterDonors = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) || label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase()); option.value.id.toString().startsWith(filterText.toLowerCase());
function focus(el) {
el.focus();
}
$: donor = 0; $: donor = 0;
$: runner = 0; $: runner = 0;
$: donors = []; $: donors = [];
$: runners = []; $: runners = [];
$: is_fixed = false; $: is_fixed = false;
$: is_paid = false; $: is_paid = false;
DonorService.donorControllerGetAll().then((val) => {
donors = val.map((r) => {
return { label: getDonorLabel(r), value: r };
});
});
RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => {
return { label: getDonorLabel(r), value: r };
});
});
$: amount_input = 0; $: amount_input = 0;
$: processed_last_submit = true; $: processed_last_submit = true;
$: is_amount_valid = amount_input > 0; $: is_amount_valid = amount_input > 0;
@@ -59,10 +45,7 @@
if (processed_last_submit === true) { if (processed_last_submit === true) {
let amount_cent = Math.floor(amount_input * 100); let amount_cent = Math.floor(amount_input * 100);
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ toast.loading($_("adding-donation"));
text: $_("adding-donation"),
duration: -1,
}).showToast();
if (is_fixed) { if (is_fixed) {
let postdata = { let postdata = {
donor, donor,
@@ -79,11 +62,8 @@
amount_input = 0; amount_input = 0;
modal_open = false; modal_open = false;
// //
Toastify({ toast.dismiss();
text: $_("donation_added"), toast.success($_("donation_added"));
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
dispatch("created", { donations: [result] }); dispatch("created", { donations: [result] });
}) })
.catch((err) => { .catch((err) => {
@@ -91,8 +71,6 @@
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
//
toast.hideToast();
}); });
} else { } else {
let postdata = { let postdata = {
@@ -107,11 +85,8 @@
amount_input = 0; amount_input = 0;
modal_open = false; modal_open = false;
// //
Toastify({ toast.dismiss();
text: $_("donation_added"), toast.success($_("donation_added"));
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
dispatch("created", { donations: [result] }); dispatch("created", { donations: [result] });
}) })
.catch((err) => { .catch((err) => {
@@ -119,12 +94,26 @@
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
//
toast.hideToast();
}); });
} }
} }
} }
onMount(async () => {
toast.loading($_("loading-donors"));
donors = (await DonorService.donorControllerGetAll()).map(
(r) => {
return { label: getDonorLabel(r), value: r };
}
);
runners = (await RunnerService.runnerControllerGetAll()).map(
(r) => {
return { label: getDonorLabel(r), value: r };
}
);
toast.dismiss();
toast.success($_("all-donors-loaded"));
});
</script> </script>
{#if modal_open} {#if modal_open}

View File

@@ -1,18 +1,14 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { DonationService } from "@odit/lfk-client-js"; import { DonationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import toast from "svelte-french-toast";
export let payment_modal_open = false; export let payment_modal_open = false;
export let original_data = {}; export let original_data = {};
export let paid_amount_input = 0; export let paid_amount_input = 0;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
$: processed_last_submit = true; $: processed_last_submit = true;
function focus(el) {
el.focus();
}
$: createbtnenabled = $: createbtnenabled =
is_paid_amount_valid && is_paid_amount_valid &&
!(paid_amount_input * 100 == original_data.paidAmount); !(paid_amount_input * 100 == original_data.paidAmount);
@@ -34,13 +30,10 @@
function submit() { function submit() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ toast.loading($_("updating-donation"));
text: $_("updating-donation"),
duration: -1,
}).showToast();
const editable = Object.assign({}, original_data); const editable = Object.assign({}, original_data);
editable.donor = editable.donor.id; editable.donor = editable.donor.id;
editable.paidAmount = paid_amount_input * 100; editable.paidAmount = Math.round(paid_amount_input * 100);
if (editable.responseType == "DISTANCEDONATION" || editable.runner) { if (editable.responseType == "DISTANCEDONATION" || editable.runner) {
editable.runner = editable.runner.id; editable.runner = editable.runner.id;
DonationService.donationControllerPutDistance( DonationService.donationControllerPutDistance(
@@ -50,40 +43,31 @@
.then((result) => { .then((result) => {
payment_modal_open = false; payment_modal_open = false;
// //
Toastify({ toast.dismiss();
text: $_("donation-updated"),
duration: 500, toast.success($_("donation-updated"));
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", dispatch("created", { donation: result });
}).showToast();
dispatch("created", { donation: response });
}) })
.catch((err) => { .catch((err) => {
// //
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
//
toast.hideToast();
}); });
} else { } else {
DonationService.donationControllerPutFixed(original_data.id, editable) DonationService.donationControllerPutFixed(original_data.id, editable)
.then((result) => { .then((result) => {
payment_modal_open = false; payment_modal_open = false;
// //
Toastify({ toast.dismiss();
text: $_("donation-updated"), toast.success($_("donation-updated"));
duration: 500, dispatch("created", { donation: result });
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
dispatch("created", { donation: response });
}) })
.catch((err) => { .catch((err) => {
// //
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
//
toast.hideToast();
}); });
} }
} }

View File

@@ -6,7 +6,8 @@
DonorService, DonorService,
RunnerService, RunnerService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import toast from 'svelte-french-toast'
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select"; import Select from "svelte-select";
let data_loaded = false; let data_loaded = false;
@@ -33,7 +34,7 @@
!(Math.floor(amount_input * 100) === original_data.amountPerDistance)) || !(Math.floor(amount_input * 100) === original_data.amountPerDistance)) ||
(original_data.responseType !== "DISTANCEDONATION" && (original_data.responseType !== "DISTANCEDONATION" &&
!(Math.floor(amount_input * 100) === original_data.amount)) || !(Math.floor(amount_input * 100) === original_data.amount)) ||
!(Math.floor(paid_amount_input * 100) === original_data.paidAmount); !(Math.floor(paid_amount_input * 100) === original_data.paidAmount);
$: save_enabled = changes_performed && is_amount_valid && is_everything_set; $: save_enabled = changes_performed && is_amount_valid && is_everything_set;
const promise = DonationService.donationControllerGetOne( const promise = DonationService.donationControllerGetOne(
@@ -69,12 +70,9 @@
function submit() { function submit() {
if (data_loaded === true && save_enabled) { if (data_loaded === true && save_enabled) {
Toastify({ toast($_("updating-donation"));
text: $_('updating-donation'),
duration: 2500,
}).showToast();
let postdata = {}; let postdata = {};
editable.paidAmount = paid_amount_input*100; editable.paidAmount = paid_amount_input * 100;
if (original_data.responseType === "DISTANCEDONATION") { if (original_data.responseType === "DISTANCEDONATION") {
editable.amountPerDistance = Math.floor(amount_input * 100); editable.amountPerDistance = Math.floor(amount_input * 100);
postdata = Object.assign(postdata, editable); postdata = Object.assign(postdata, editable);
@@ -87,11 +85,7 @@
.then((resp) => { .then((resp) => {
Object.assign(original_data, editable); Object.assign(original_data, editable);
original_data = original_data; original_data = original_data;
Toastify({ toast.success($_("donation-updated"));
text: $_('donation-updated'),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}) })
.catch((err) => {}); .catch((err) => {});
} else { } else {
@@ -102,11 +96,7 @@
.then((resp) => { .then((resp) => {
Object.assign(original_data, editable); Object.assign(original_data, editable);
original_data = original_data; original_data = original_data;
Toastify({ toast.success($_("donation-updated"));
text: $_('donation-updated'),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}) })
.catch((err) => {}); .catch((err) => {});
} }
@@ -116,11 +106,7 @@
function deleteDonation() { function deleteDonation() {
DonationService.donationControllerRemove(original_data.id, false) DonationService.donationControllerRemove(original_data.id, false)
.then((resp) => { .then((resp) => {
Toastify({ toast($_("donation-deleted"));
text: $_('donation-deleted'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./"); location.replace("./");
}) })
.catch((err) => { .catch((err) => {
@@ -131,7 +117,7 @@
</script> </script>
{#await promise} {#await promise}
{$_('loading-donation-details')} {$_("loading-donation-details")}
{:then} {:then}
<section class="container p-5 select-none"> <section class="container p-5 select-none">
<div class="flex flex-row mb-4"> <div class="flex flex-row mb-4">
@@ -144,12 +130,15 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
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> d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</li> </li>
<li class="flex items-center ml-2"> <li class="flex items-center ml-2">
<a class="mr-2" href="./">{$_('donations')}</a><svg <a class="mr-2" href="./">{$_("donations")}</a><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
@@ -159,12 +148,10 @@
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"
x1="5" ><line x1="5" y1="12" x2="19" y2="12" />
y1="12" <polyline points="12 5 19 12 12 19" /></svg
x2="19" >
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="mr-2">{original_data.id}</span> <span class="mr-2">{original_data.id}</span>
@@ -175,28 +162,32 @@
</div> </div>
<div class="mb-8 text-3xl font-extrabold leading-tight"> <div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.donor.firstname} {original_data.donor.firstname}
{original_data.donor.middlename || ''} {original_data.donor.middlename || ""}
{original_data.donor.lastname} {original_data.donor.lastname}
&gt; &gt;
{#if original_data.responseType == 'DISTANCEDONATION'} {#if original_data.responseType == "DISTANCEDONATION"}
{original_data.runner.firstname} {original_data.runner.firstname}
{original_data.runner.middlename || ''} {original_data.runner.middlename || ""}
{original_data.runner.lastname} {original_data.runner.lastname}
{:else} {:else}
{$_('fixed-donation')}: {$_("fixed-donation")}:
{amount_input.toFixed(2).toLocaleString('de-DE', { valute: 'EUR' })} {amount_input.toFixed(2).toLocaleString("de-DE", { valute: "EUR" })}
{/if} {/if}
<span data-id="donation_actions_${original_data.id}"> <span data-id="donation_actions_${original_data.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:DELETE")}
{#if delete_triggered} {#if delete_triggered}
<button <button
on:click={deleteDonation} on:click={deleteDonation}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:">{$_('confirm-deletion')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
>{$_("confirm-deletion")}</button
>
<button <button
on:click={() => { on:click={() => {
delete_triggered = !delete_triggered; delete_triggered = !delete_triggered;
}} }}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:">{$_('cancel')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:"
>{$_("cancel")}</button
>
{/if} {/if}
{#if !delete_triggered} {#if !delete_triggered}
<button <button
@@ -204,7 +195,9 @@
delete_triggered = true; delete_triggered = true;
}} }}
type="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:">{$_('delete-donation')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
>{$_("delete-donation")}</button
>
{/if} {/if}
{/if} {/if}
{#if !delete_triggered} {#if !delete_triggered}
@@ -213,72 +206,91 @@
class:opacity-50={!save_enabled} class:opacity-50={!save_enabled}
type="button" type="button"
on:click={submit} on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:">{$_('save-changes')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:"
>{$_("save-changes")}</button
>
{/if} {/if}
</span> </span>
</div> </div>
<!-- --> <!-- -->
<div> <div>
<span class="font-medium text-gray-700"
>{$_("total-donation-amount")}:</span
>
<span <span
class="font-medium text-gray-700">{$_('total-donation-amount')}:</span> >{(editable.amount / 100)
<span>{(editable.amount / 100)
.toFixed(2) .toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</span> .toLocaleString("de-DE", { valute: "EUR" })}</span
>
| |
<span class="font-medium text-gray-700">{$_("paid-amount")}:</span>
<span <span
class="font-medium text-gray-700">{$_('paid-amount')}:</span> >{(editable.paidAmount / 100)
<span>{(editable.paidAmount / 100)
.toFixed(2) .toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</span> .toLocaleString("de-DE", { valute: "EUR" })}</span
>
| |
<span <span class="font-medium text-gray-700">{$_("status")}:</span>
class="font-medium text-gray-700">{$_('status')}:</span> {#if editable.status == "PAID"}
{#if editable.status =="PAID"} <span
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('paid')}</span> >{$_("paid")}</span
{:else} >
<span {:else}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('open')}</span> <span
{/if} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("open")}</span
>
{/if}
</div> </div>
<br> <br />
<div class=" w-full"> <div class=" w-full">
<label <label for="donor" class="block font-medium text-gray-700"
for="donor" >{$_("donor")}</label
class="block font-medium text-gray-700">{$_('donor')}</label> >
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)} itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={current_donors} items={current_donors}
showChevron={true} showChevron={true}
placeholder={$_('search-for-donor-name-or-id')} placeholder={$_("search-for-donor-name-or-id")}
noOptionsMessage={$_('no-donors-found')} noOptionsMessage={$_("no-donors-found")}
bind:selectedValue={donor} bind:selectedValue={donor}
on:select={(selectedValue) => {editable.donor = selectedValue.detail.value; editable.donor.donationAmount=original_data.donor.donationAmount; editable.donor.paidDonationAmount =original_data.donor.paidDonationAmount}} on:select={(selectedValue) => {
on:clear={() => (editable.donor = null)} /> 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> </div>
{#if original_data.responseType == 'DISTANCEDONATION'} {#if original_data.responseType == "DISTANCEDONATION"}
<div class=" w-full"> <div class=" w-full">
<label <label for="donor" class="block font-medium text-gray-700"
for="donor" >{$_("runner")}</label
class="block font-medium text-gray-700">{$_('runner')}</label> >
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)} itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={current_runners} items={current_runners}
showChevron={true} showChevron={true}
placeholder={$_('search-for-runner-by-name-or-id')} placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_('no-runners-found')} noOptionsMessage={$_("no-runners-found")}
bind:selectedValue={runner} bind:selectedValue={runner}
on:select={(selectedValue) => (editable.runner = selectedValue.detail.value)} on:select={(selectedValue) =>
on:clear={() => (editable.runner = null)} /> (editable.runner = selectedValue.detail.value)}
on:clear={() => (editable.runner = null)}
/>
</div> </div>
{/if} {/if}
<div class=" w-full"> <div class=" w-full">
<label for="lastname" class="font-medium text-gray-700"> <label for="lastname" class="font-medium text-gray-700">
{#if original_data.responseType == 'DISTANCEDONATION'} {#if original_data.responseType == "DISTANCEDONATION"}
{$_('amount-per-kilometer')} {$_("amount-per-kilometer")}
{:else}{$_('donation-amount')}{/if} {:else}{$_("donation-amount")}{/if}
</label> </label>
<div class="mt-1 flex rounded-md shadow-sm"> <div class="mt-1 flex rounded-md shadow-sm">
<input <input
@@ -291,49 +303,61 @@
step="0.01" step="0.01"
name="donation_amount_eur" name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 p-2" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="2.00" /> placeholder="2.00"
/>
<span <span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 "></span> class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500"
></span
>
</div> </div>
{#if !is_amount_valid} {#if !is_amount_valid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('donation-amount-must-be-greater-that-0-00eur')} >
{$_("donation-amount-must-be-greater-that-0-00eur")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="w-full"> <div class="w-full">
<label <label for="token" class="block text-sm font-medium text-gray-700"
for="token" >{$_("paid-amount")}</label
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"> <div
class="inline-flex border-gray-300 border rounded-l-md rounded-r-md bg-gray-50 text-gray-500 w-full"
>
<input <input
autocomplete="off" autocomplete="off"
class:border-red-500={!is_amount_valid} class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid} class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid} class:focus:ring-red-500={!is_amount_valid}
bind:value={paid_amount_input} bind:value={paid_amount_input}
type="number" type="number"
step="0.01" step="0.01"
name="donation_amount_eur" 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" 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" /> placeholder="2.00"
<button />
on:click={ <button
()=>{ on:click={() => {
paid_amount_input=paid_amount_input = (original_data.amount/100).toFixed(2); 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 p-r-2 text-indigo-300 hover:text-indigo-700 text-sm"
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"></span> >MAX</button
</div> >
{#if !is_paid_amount_valid} <span
<span class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> ></span
{$_('payment-amount-must-be-greater-than-0-00eur')} >
</span> </div>
{/if} {#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>
</section> </section>
{:catch error} {:catch error}

View File

@@ -9,27 +9,26 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("donations")} {$_("donations")}
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")} </h4>
<button {#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")}
on:click={() => { <button
modal_open = true; 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" 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:w-auto sm:text-sm"
{$_("add-donation")} >
</button> {$_("add-donation")}
{/if} </button>
</span> {/if}
<DonationsOverview bind:current_donations bind:addDonations /> <DonationsOverview bind:current_donations bind:addDonations />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")}
<AddDonationModal <AddDonationModal
on:created={(event) => { on:created={(event) => {
console.log(event)
addDonations(event.detail.donations); addDonations(event.detail.donations);
}} }}
bind:modal_open bind:modal_open

View File

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

View File

@@ -23,11 +23,14 @@
import DonationStatus from "./DonationStatus.svelte"; import DonationStatus from "./DonationStatus.svelte";
import DonationTableAction from "./DonationTableAction.svelte"; import DonationTableAction from "./DonationTableAction.svelte";
import DeleteDonationModal from "./DeleteDonationModal.svelte"; import DeleteDonationModal from "./DeleteDonationModal.svelte";
import {
donationDonorFilter,
donationRunnerFilter,
} from "../shared/tablefilters";
import toast from "svelte-french-toast";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: active_edits = []; $: active_edits = [];
$: selectedDonations =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected = $: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || []; $table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: dataLoaded = false; $: dataLoaded = false;
@@ -54,7 +57,7 @@
cell: (info) => { cell: (info) => {
return renderComponent(DonationDonor, { donor: info.getValue() }); return renderComponent(DonationDonor, { donor: info.getValue() });
}, },
filterFn: `includesString`, filterFn: `donor`,
}, },
{ {
accessorKey: "runner", accessorKey: "runner",
@@ -62,7 +65,7 @@
cell: (info) => { cell: (info) => {
return renderComponent(DonationRunner, { runner: info.getValue() }); return renderComponent(DonationRunner, { runner: info.getValue() });
}, },
filterFn: `includesString`, filterFn: `runner`,
}, },
{ {
accessorKey: "amountPerDistance", accessorKey: "amountPerDistance",
@@ -139,6 +142,10 @@
pageSize: 50, pageSize: 50,
}, },
}, },
filterFns: {
donor: donationDonorFilter,
runner: donationRunnerFilter,
},
enableRowSelection: true, enableRowSelection: true,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(), getFilteredRowModel: getFilteredRowModel(),
@@ -156,19 +163,16 @@
...options, ...options,
data: current_donations, data: current_donations,
})); }));
Toastify({ toast($_("donation-deleted"));
text: $_("donation-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
} }
onMount(async () => { onMount(async () => {
let page = 0; let page = 0;
let pagesize = 300;
while (page >= 0) { while (page >= 0) {
const donations = await DonationService.donationControllerGetAll( const donations = await DonationService.donationControllerGetAll(
page, page,
500 pagesize
); );
if (donations.length == 0) { if (donations.length == 0) {
page = -2; page = -2;
@@ -183,7 +187,6 @@
dataLoaded = true; dataLoaded = true;
page++; page++;
} }
console.log("All donations loaded");
}); });
</script> </script>
@@ -192,9 +195,12 @@
payment_modal_open={active_edits.length > 0} payment_modal_open={active_edits.length > 0}
paid_amount_input={(active_edits[0]?.paidAmount || 0) / 100} paid_amount_input={(active_edits[0]?.paidAmount || 0) / 100}
on:created={(event) => { on:created={(event) => {
current_donations[ current_donations = current_donations.map((d)=>{
current_donations.findIndex((d) => d.id === event.detail.donation.id) if(d.id === event.detail.donation.id){
].paidAmount = event.detail.donation.paidAmount; d.paidAmount = event.detail.donation.paidAmount;
}
return d;
})
options.update((options) => ({ options.update((options) => ({
...options, ...options,
data: current_donations, data: current_donations,
@@ -225,13 +231,13 @@
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_("datatable.search")} placeholder={$_("datatable.search")}
aria-label={$_("datatable.search")} aria-label={$_("datatable.search")}
class="mb-4" class="mb-2 w-full sm:w-auto mt-1 sm:mt-0 p-2 rounded-md border"
/> />
<div <div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll" class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
> >
<table class="w-full"> <table class="w-full">
<thead> <thead class="border-b border-gray-400">
{#each $table.getHeaderGroups() as headerGroup} {#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none"> <tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px"> <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
@@ -250,7 +256,7 @@
</thead> </thead>
<tbody> <tbody>
{#each $table.getRowModel().rows as row} {#each $table.getRowModel().rows as row}
<tr> <tr class="odd:bg-white even:bg-gray-100">
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px"> <td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement <InputElement
type="checkbox" type="checkbox"
@@ -277,3 +283,9 @@
<TableBottom {table} {selected} /> <TableBottom {table} {selected} />
{/if} {/if}
{/if} {/if}
<style>
table tbody tr td:nth-child(2) {
font-family: monospace;
}
</style>

View File

@@ -5,8 +5,9 @@
import { DonorService } from "@odit/lfk-client-js"; import { DonorService } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
import isMobilePhone from "validator/es/lib/isMobilePhone"; import isMobilePhone from "validator/es/lib/isMobilePhone";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import toast from "svelte-french-toast";
export let modal_open; export let modal_open;
let firstname_input; let firstname_input;
let lastname_input; let lastname_input;
@@ -73,10 +74,7 @@
function submit() { function submit() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ toast.loading($_("donor-is-being-added"));
text: $_("donor-is-being-added"),
duration: -1,
}).showToast();
let address = {}; let address = {};
if (address_checked === true) { if (address_checked === true) {
address = { address = {
@@ -110,11 +108,8 @@
email_input_value = ""; email_input_value = "";
modal_open = false; modal_open = false;
// //
Toastify({ toast.dismiss();
text: $_("donor-added"), toast.success($_("donor-added"));
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
dispatch("created", { donors: [result] }); dispatch("created", { donors: [result] });
}) })
.catch((err) => { .catch((err) => {
@@ -122,8 +117,6 @@
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
//
toast.hideToast();
}); });
} }
} }

View File

@@ -3,7 +3,7 @@
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { DonorService } from "@odit/lfk-client-js"; import { DonorService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
export let modal_open; export let modal_open;
export let delete_donor; export let delete_donor;

View File

@@ -2,16 +2,16 @@
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import store from "../../store"; import store from "../../store";
import { DonorService, DonationService } from "@odit/lfk-client-js"; import { DonorService, DonationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte"; import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte";
import toast from "svelte-french-toast";
let data_loaded = false; let data_loaded = false;
export let params; export let params;
$: delete_triggered = false; $: delete_triggered = false;
$: original_data = {}; $: original_data = {};
$: editable = {}; $: editable = {};
$: current_donations = [];
$: changes_performed = !( $: changes_performed = !(
JSON.stringify(original_data) === JSON.stringify(editable) JSON.stringify(original_data) === JSON.stringify(editable)
); );
@@ -28,11 +28,6 @@
isPhoneValidOrEmpty && isPhoneValidOrEmpty &&
((isAddress1Valid && iszipcodevalid && iscityvalid) || ((isAddress1Valid && iszipcodevalid && iscityvalid) ||
editable.address_checked === false); editable.address_checked === false);
const donation_promise = DonationService.donationControllerGetAll().then(
(val) => {
current_donations = val;
}
);
const promise = DonorService.donorControllerGetOne(params.donorid).then( const promise = DonorService.donorControllerGetOne(params.donorid).then(
(data) => { (data) => {
data_loaded = true; data_loaded = true;
@@ -62,27 +57,22 @@
let delete_donor = {}; let delete_donor = {};
function submit() { function submit() {
if (data_loaded === true && save_enabled) { if (data_loaded === true && save_enabled) {
Toastify({ toast($_("donor-is-being-updated"));
text: $_("donor-is-being-updated"),
duration: 2500,
}).showToast();
editable.address.country = "DE"; editable.address.country = "DE";
if (editable.address_checked === false) { if (editable.address_checked === false) {
editable.address = null; editable.address = null;
} }
if (editable.email) editable.email = editable.email; if (editable.email) editable.email = editable.email;
else editable.email = null;
if (editable.phone) editable.phone = editable.phone; if (editable.phone) editable.phone = editable.phone;
else editable.phone = null;
if (editable.middlename) editable.middlename = editable.middlename; if (editable.middlename) editable.middlename = editable.middlename;
editable.receiptNeeded = editable.address_checked; editable.receiptNeeded = editable.address_checked;
DonorService.donorControllerPut(original_data.id, editable) DonorService.donorControllerPut(original_data.id, editable)
.then((resp) => { .then((resp) => {
Object.assign(original_data, editable); Object.assign(original_data, editable);
original_data = original_data; original_data = original_data;
Toastify({ toast.success($_("updated-donor"));
text: $_("updated-donor"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}) })
.catch((err) => {}); .catch((err) => {});
} else { } else {
@@ -91,11 +81,7 @@
function deleteDonor() { function deleteDonor() {
DonorService.donorControllerRemove(original_data.id, false) DonorService.donorControllerRemove(original_data.id, false)
.then((resp) => { .then((resp) => {
Toastify({ toast($_("donor-deleted"));
text: $_("donor-deleted"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./"); location.replace("./");
}) })
.catch((err) => { .catch((err) => {
@@ -106,8 +92,8 @@
</script> </script>
<ConfirmDonorDeletion bind:modal_open bind:delete_donor /> <ConfirmDonorDeletion bind:modal_open bind:delete_donor />
{#await promise && donation_promise} {#await promise}
{$_('loading-donor-details')} {$_("loading-donor-details")}
{:then} {:then}
<section class="container p-5 select-none"> <section class="container p-5 select-none">
<div class="flex flex-row mb-4"> <div class="flex flex-row mb-4">
@@ -120,12 +106,15 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
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
>
</li> </li>
<li class="flex items-center ml-2"> <li class="flex items-center ml-2">
<a class="mr-2" href="./">{$_('donors')}</a><svg <a class="mr-2" href="./">{$_("donors")}</a><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
@@ -135,17 +124,17 @@
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"
x1="5" ><line x1="5" y1="12" x2="19" y2="12" />
y1="12" <polyline points="12 5 19 12 12 19" /></svg
x2="19" >
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="mr-2">{original_data.firstname} <span class="mr-2"
{original_data.middlename || ''} >{original_data.firstname}
{original_data.lastname}</span> {original_data.middlename || ""}
{original_data.lastname}</span
>
</li> </li>
</ol> </ol>
</nav> </nav>
@@ -153,19 +142,23 @@
</div> </div>
<div class="mb-8 text-3xl font-extrabold leading-tight"> <div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.firstname} {original_data.firstname}
{original_data.middlename || ''} {original_data.middlename || ""}
{original_data.lastname} {original_data.lastname}
<span data-id="donor_actions_${editable.id}"> <span data-id="donor_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:DELETE")}
{#if delete_triggered} {#if delete_triggered}
<button <button
on:click={deleteDonor} on:click={deleteDonor}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:">{$_('confirm-deletion')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
>{$_("confirm-deletion")}</button
>
<button <button
on:click={() => { on:click={() => {
delete_triggered = !delete_triggered; delete_triggered = !delete_triggered;
}} }}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:">{$_('cancel')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:"
>{$_("cancel")}</button
>
{/if} {/if}
{#if !delete_triggered} {#if !delete_triggered}
<button <button
@@ -173,7 +166,9 @@
delete_triggered = true; delete_triggered = true;
}} }}
type="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:">{$_('delete-donor')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
>{$_("delete-donor")}</button
>
{/if} {/if}
{/if} {/if}
{#if !delete_triggered} {#if !delete_triggered}
@@ -182,135 +177,154 @@
class:opacity-50={!save_enabled} class:opacity-50={!save_enabled}
type="button" type="button"
on:click={submit} on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:">{$_('save-changes')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:"
>{$_("save-changes")}</button
>
{/if} {/if}
</span> </span>
</div> </div>
<!-- --> <!-- -->
<div> <div>
<span class="font-medium text-gray-700"
>{$_("total-donation-amount")}:</span
>
<span <span
class="font-medium text-gray-700">{$_('total-donation-amount')}:</span> >{(editable.donationAmount / 100)
<span>{(editable.donationAmount / 100)
.toFixed(2) .toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</span> .toLocaleString("de-DE", { valute: "EUR" })}</span
>
| |
<span class="font-medium text-gray-700">{$_("total-paid-amount")}:</span>
<span <span
class="font-medium text-gray-700">{$_('total-paid-amount')}:</span> >{(editable.paidDonationAmount / 100)
<span>{(editable.paidDonationAmount / 100)
.toFixed(2) .toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</span> .toLocaleString("de-DE", { valute: "EUR" })}</span
>
<br /> <br />
<span class="font-medium text-gray-700">{$_('donations')}:</span> <span class="font-medium text-gray-700">{$_("donations")}:</span>
{#if current_donations.filter((d) => d.donor.id == editable.id).length > 0} {#if original_data.donations.length > 0}
{#each current_donations.filter((o) => o.donor.id == editable.id) as d} {#each original_data.donations as d}
{#if d.responseType === 'DISTANCEDONATION'} {#if d.responseType === "DISTANCEDONATION"}
<a <a
href="../donations/{d.id}" href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1"
>{d.runner.firstname}
{d.runner.middlename || ""} {d.runner.middlename || ""}
{d.runner.lastname}</a> {d.runner.lastname}</a
>
{:else} {:else}
<a <a
href="../donations/{d.id}" 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')}: 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) {(d.amount / 100)
.toFixed(2) .toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</a> .toLocaleString("de-DE", { valute: "EUR" })}</a
>
{/if} {/if}
{/each} {/each}
{:else}{$_('donor-has-no-associated-donations')}{/if} {:else}{$_("donor-has-no-associated-donations")}{/if}
</div> </div>
<div class=" w-full"> <div class=" w-full">
<label <label for="firstname" class="font-medium text-gray-700"
for="firstname" >{$_("first-name")}</label
class="font-medium text-gray-700">{$_('first-name')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('first-name')} placeholder={$_("first-name")}
type="text" type="text"
class:border-red-500={!isFirstnameValid} class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid} class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid} class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname} bind:value={editable.firstname}
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isFirstnameValid} {#if !isFirstnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('first-name-is-required')} >
{$_("first-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class=" w-full"> <div class=" w-full">
<label <label for="middlename" class="font-medium text-gray-700"
for="middlename" >{$_("middle-name")}</label
class="font-medium text-gray-700">{$_('middle-name')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('middle-name')} placeholder={$_("middle-name")}
type="text" type="text"
bind:value={editable.middlename} bind:value={editable.middlename}
name="middlename" name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div> </div>
<div class=" w-full"> <div class=" w-full">
<label <label for="lastname" class="font-medium text-gray-700"
for="lastname" >{$_("last-name")}</label
class="font-medium text-gray-700">{$_('last-name')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('last-name')} placeholder={$_("last-name")}
type="text" type="text"
bind:value={editable.lastname} bind:value={editable.lastname}
class:border-red-500={!isLastnameValid} class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid} class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid} class:focus:ring-red-500={!isLastnameValid}
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isLastnameValid} {#if !isLastnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('last-name-is-required')} >
{$_("last-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class=" w-full"> <div class=" w-full">
<label <label for="email" class="font-medium text-gray-700"
for="email" >{$_("e-mail-adress")}</label
class="font-medium text-gray-700">{$_('e-mail-adress')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('e-mail-adress')} placeholder={$_("e-mail-adress")}
type="email" type="email"
bind:value={editable.email} bind:value={editable.email}
class:border-red-500={!isEmailValid} class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid} class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid} class:focus:ring-red-500={!isEmailValid}
name="email" name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isEmailValid} {#if !isEmailValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-email-is-required')} >
{$_("valid-email-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class=" w-full"> <div class=" w-full">
<label for="phone" class="font-medium text-gray-700">{$_('phone')}</label> <label for="phone" class="font-medium text-gray-700">{$_("phone")}</label>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('phone')} placeholder={$_("phone")}
type="tel" type="tel"
class:border-red-500={!isPhoneValidOrEmpty} class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty} class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty} class:focus:ring-red-500={!isPhoneValidOrEmpty}
bind:value={editable.phone} bind:value={editable.phone}
name="phone" name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isPhoneValidOrEmpty} {#if !isPhoneValidOrEmpty}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-international-phone-number-is-required')} >
{$_("valid-international-phone-number-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
@@ -321,19 +335,20 @@
id="comments" id="comments"
name="comments" name="comments"
type="checkbox" type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div> </div>
<div class="ml-3 "> <div class="ml-3">
<label <label for="comments" class="font-medium text-gray-700"
for="comments" >{$_("receipt-needed")}</label
class="font-medium text-gray-700">{$_('receipt-needed')}</label> >
</div> </div>
</div> </div>
{#if editable.address_checked === true} {#if editable.address_checked === true}
<div class="col-span-6"> <div class="col-span-6">
<label <label for="address1" class="block font-medium text-gray-700"
for="address1" >{$_("address")}</label
class="block font-medium text-gray-700">{$_('address')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder="Address" placeholder="Address"
@@ -343,65 +358,72 @@
bind:value={editable.address.address1} bind:value={editable.address.address1}
type="text" type="text"
name="address1" name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isAddress1Valid} {#if !isAddress1Valid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('address-is-required')} >
{$_("address-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label for="address2" class="block font-medium text-gray-700"
for="address2" >{$_("apartment-suite-etc")}</label
class="block font-medium text-gray-700">{$_('apartment-suite-etc')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('apartment-suite-etc')} placeholder={$_("apartment-suite-etc")}
bind:value={editable.address.address2} bind:value={editable.address.address2}
type="text" type="text"
name="address2" name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label for="zipcode" class="block font-medium text-gray-700"
for="zipcode" >{$_("zip-postal-code")}</label
class="block font-medium text-gray-700">{$_('zip-postal-code')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('zip-postal-code')} placeholder={$_("zip-postal-code")}
class:border-red-500={!iszipcodevalid} class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid} class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid} class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode} bind:value={editable.address.postalcode}
type="text" type="text"
name="zipcode" name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !iszipcodevalid} {#if !iszipcodevalid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-zipcode-postal-code-is-required')} >
{$_("valid-zipcode-postal-code-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label for="city" class="block font-medium text-gray-700"
for="city" >{$_("city")}</label
class="block font-medium text-gray-700">{$_('city')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('city')} placeholder={$_("city")}
class:border-red-500={!iscityvalid} class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid} class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid} class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city} bind:value={editable.address.city}
type="text" type="text"
name="city" name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !iscityvalid} {#if !iscityvalid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-city-is-required')} >
{$_("valid-city-is-required")}
</span> </span>
{/if} {/if}
</div> </div>

View File

@@ -4,7 +4,7 @@
</script> </script>
{#if !donations || donations.length == 0} {#if !donations || donations.length == 0}
{$_('donor-has-no-associated-donations')} {$_("donor-has-no-associated-donations")}
{:else} {:else}
{#each donations as donation} {#each donations as donation}
{#if donation.responseType === "DISTANCEDONATION"} {#if donation.responseType === "DISTANCEDONATION"}
@@ -17,10 +17,10 @@
> >
{:else} {:else}
<a <a
href="../donations/{d.id}" href="../donations/{donation.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1"
>{$_("fixed-donation")}: >{$_("fixed-donation")}:
{(d.amount / 100) {(donation.amount / 100)
.toFixed(2) .toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</a .toLocaleString("de-DE", { valute: "EUR" })}</a
> >

View File

@@ -9,61 +9,61 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("donors")} {$_("donors")}
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:CREATE")} </h4>
<button {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:CREATE")}
on:click={() => { <button
modal_open = true; 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" 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:w-auto sm:text-sm mt-1 sm:mt-0"
{$_("add-donor")} >
</button> {$_("add-donor")}
{/if} </button>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")} {/if}
<button {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")}
on:click={() => { <button
const data = current_donors on:click={() => {
.filter((d) => d.receiptNeeded === true) const data = current_donors
.map(function (d) { .filter((d) => d.receiptNeeded === true)
d.address.address2 = .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}`; d.address.address2 === "" ? "" : " " + d.address.address2;
return [ const address = `${d.address.address1}${d.address.address2}, ${d.address.postalcode} ${d.address.city}, ${d.address.country}`;
d.firstname, return [
d.middlename, d.firstname,
d.lastname, d.middlename,
d.paidDonationAmount, d.lastname,
address, (d.paidDonationAmount/100).toFixed(2),
]; 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"); let csv = `${$_("csv_import__firstname")};${$_(
hiddenElement.href = "data:text/csv;charset=utf-8," + encodeURI(csv); "csv_import__middlename"
hiddenElement.target = "_blank"; )};${$_("csv_import__lastname")};${$_(
hiddenElement.download = `${$_( "total_donation_amount_in_eur"
"filename_sponsoringquittungsliste" )};${$_("address")}\n`;
)}.csv`; data.forEach(function (row) {
hiddenElement.click(); csv += row.join(";");
hiddenElement.remove(); csv += "\n";
}} });
type="button" let hiddenElement = document.createElement("a");
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" hiddenElement.href = "data:text/csv;charset=utf-8," + encodeURI(csv);
> hiddenElement.target = "_blank";
{$_("sponsoring-quittungs-liste_herunterladen")} hiddenElement.download = `${$_(
</button> "filename_sponsoringquittungsliste"
{/if} )}.csv`;
</span> 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:w-auto sm:text-sm mt-1 sm:mt-0"
>
{$_("sponsoring-quittungs-liste_herunterladen")}
</button>
{/if}
<DonorsOverview bind:current_donors bind:addDonors /> <DonorsOverview bind:current_donors bind:addDonors />
</section> </section>

View File

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

View File

@@ -1,10 +1,9 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { DonationService, DonorService } from "@odit/lfk-client-js"; import { DonorService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import DonorsEmptyState from "./DonorsEmptyState.svelte"; import DonorsEmptyState from "./DonorsEmptyState.svelte";
import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte"; import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte";
import Toastify from "toastify-js";
import TableBottom from "../shared/TableBottom.svelte"; import TableBottom from "../shared/TableBottom.svelte";
import { import {
createSvelteTable, createSvelteTable,
@@ -22,9 +21,10 @@
import TableActions from "../shared/TableActions.svelte"; import TableActions from "../shared/TableActions.svelte";
import DonorAddress from "./DonorAddress.svelte"; import DonorAddress from "./DonorAddress.svelte";
import DonorDonations from "./DonorDonations.svelte"; import DonorDonations from "./DonorDonations.svelte";
import { filterAddress, filterName } from "../shared/tablefilters";
import toast from "svelte-french-toast";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: current_donations = [];
$: selectedDonors = $: selectedDonors =
$table?.getSelectedRowModel().rows.map((row) => row.original) || []; $table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected = $: selected =
@@ -32,8 +32,6 @@
$: dataLoaded = false; $: dataLoaded = false;
let modal_open = false;
let delete_donor = {};
export let current_donors = []; export let current_donors = [];
export const addDonors = (donors) => { export const addDonors = (donors) => {
current_donors = current_donors.concat(...donors); current_donors = current_donors.concat(...donors);
@@ -61,7 +59,7 @@
return `${d.firstname} ${d.lastname}`; return `${d.firstname} ${d.lastname}`;
} }
}, },
filterFn: `includesString`, filterFn: `name`,
}, },
{ {
accessorKey: "address", accessorKey: "address",
@@ -69,16 +67,13 @@
cell: (info) => { cell: (info) => {
return renderComponent(DonorAddress, { address: info.getValue() }); return renderComponent(DonorAddress, { address: info.getValue() });
}, },
filterFn: `includesString`, filterFn: `address`,
}, },
{ {
accessorKey: "sponsorings", accessorKey: "donations",
header: () => $_("sponsorings"), header: () => $_("sponsorings"),
cell: (info) => { cell: (info) => {
const donations = current_donations.filter( return renderComponent(DonorDonations, { donations: info.getValue() });
(d) => d?.donor?.id == info.row.original.id
);
return renderComponent(DonorDonations, { donations });
}, },
enableColumnFilter: false, enableColumnFilter: false,
}, },
@@ -131,6 +126,10 @@
pageSize: 50, pageSize: 50,
}, },
}, },
filterFns: {
name: filterName,
address: filterAddress,
},
enableRowSelection: true, enableRowSelection: true,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(), getFilteredRowModel: getFilteredRowModel(),
@@ -148,18 +147,14 @@
onMount(async () => { onMount(async () => {
let page = 0; let page = 0;
let pagesize = 300;
while (page >= 0) { while (page >= 0) {
const donors = await DonorService.donorControllerGetAll(page, 500); const donors = await DonorService.donorControllerGetAll(page, pagesize);
const donations = await DonationService.donationControllerGetAll( if (donors.length == 0) {
page,
500
);
if (donors.length == 0 && donations.length == 0) {
page = -2; page = -2;
} }
current_donors = current_donors.concat(...donors); current_donors = current_donors.concat(...donors);
current_donations = current_donations.concat(...donors);
options.update((options) => ({ options.update((options) => ({
...options, ...options,
data: current_donors, data: current_donors,
@@ -168,8 +163,6 @@
dataLoaded = true; dataLoaded = true;
page++; page++;
} }
console.log("All donors loaded");
}); });
</script> </script>
@@ -178,12 +171,10 @@
active_deletes = active_deletes.filter((a) => a.id !== event.detail.id); active_deletes = active_deletes.filter((a) => a.id !== event.detail.id);
}} }}
on:delete={async (event) => { on:delete={async (event) => {
toast.loading($_("deleting-donor"));
await DonorService.donorControllerRemove(event.detail.id, true); await DonorService.donorControllerRemove(event.detail.id, true);
Toastify({ toast.dismiss();
text: $_("donor-deleted"), toast($_("donor-deleted"));
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donors = current_donors.filter((d) => d.id !== event.detail.id); current_donors = current_donors.filter((d) => d.id !== event.detail.id);
active_deletes = active_deletes.filter((a) => a.id !== event.detail.id); active_deletes = active_deletes.filter((a) => a.id !== event.detail.id);
options.update((options) => ({ options.update((options) => ({
@@ -194,7 +185,6 @@
modal_open={active_deletes.length > 0} modal_open={active_deletes.length > 0}
delete_donor={active_deletes[0]} delete_donor={active_deletes[0]}
/> />
{active_deletes.length}
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")} {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")}
{#if !dataLoaded} {#if !dataLoaded}
<div <div
@@ -212,13 +202,13 @@
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_("datatable.search")} placeholder={$_("datatable.search")}
aria-label={$_("datatable.search")} aria-label={$_("datatable.search")}
class="mb-4" class="mb-2 w-full sm:w-auto mt-1 sm:mt-0 p-2 rounded-md border"
/> />
<div <div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll" class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
> >
<table class="w-full"> <table class="w-full">
<thead> <thead class="border-b border-gray-400">
{#each $table.getHeaderGroups() as headerGroup} {#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none"> <tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px"> <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
@@ -237,7 +227,7 @@
</thead> </thead>
<tbody> <tbody>
{#each $table.getRowModel().rows as row} {#each $table.getRowModel().rows as row}
<tr> <tr class="odd:bg-white even:bg-gray-100">
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px"> <td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement <InputElement
type="checkbox" type="checkbox"
@@ -264,3 +254,9 @@
<TableBottom {table} {selected} /> <TableBottom {table} {selected} />
{/if} {/if}
{/if} {/if}
<style>
table tbody tr td:nth-child(2) {
font-family: monospace;
}
</style>

View File

@@ -1,7 +1,7 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
export let modal_open; export let modal_open;
(function () { (function () {
document.onkeydown = function (e) { document.onkeydown = function (e) {
@@ -25,43 +25,51 @@
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;
}}> }}
>
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" /> data-id="modal_backdrop"
/>
</div> </div>
<span <span
class="hidden sm:inline-block sm:align-middle sm:h-screen" class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span> aria-hidden="true">&#8203;</span
>
<div <div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline"> aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg <svg
fill="currentColor" fill="currentColor"
class="h-6 w-6 text-blue-600" class="h-6 w-6 text-blue-600"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M14 20v2H2v-2h12zM14.586.686l7.778 7.778L20.95 9.88l-1.06-.354L17.413 12l5.657 5.657-1.414 1.414L16 13.414l-2.404 2.404.283 1.132-1.415 1.414-7.778-7.778 1.415-1.414 1.13.282 6.294-6.293-.353-1.06L14.586.686z" /></svg> d="M14 20v2H2v-2h12zM14.586.686l7.778 7.778L20.95 9.88l-1.06-.354L17.413 12l5.657 5.657-1.414 1.414L16 13.414l-2.404 2.404.283 1.132-1.415 1.414-7.778-7.778 1.415-1.414 1.13.282 6.294-6.293-.353-1.06L14.586.686z"
/></svg
>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium"> <h3 class="text-lg leading-6 font-medium">
{$_('read-license')} {$_("read-license")}
</h3> </h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500">{currentlicense}</p> <p class="text-sm text-gray-500">{currentlicense}</p>
@@ -78,8 +86,9 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm"
{$_('close')} >
{$_("close")}
</button> </button>
</div> </div>
</div> </div>
@@ -90,19 +99,21 @@
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12"> <div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8"> <div class="text-center mb-8">
<h1 <h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"> class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"
{$_('about')} >
{$_("about")}
🧾 🧾
</h1> </h1>
<p <p
class="mt-2 max-w-xl mx-auto text-xl lg:max-w-3xl lg:text-2xl text-gray-300"> class="mt-2 max-w-xl mx-auto text-xl lg:max-w-3xl lg:text-2xl text-gray-300"
>
Lauf für Kaya! Lauf für Kaya!
<strong class="text-white font-medium"> <strong class="text-white font-medium">
{$_('by')} {$_("by")}
<a href="https://odit.services" class="underline">ODIT.Services</a> <a href="https://odit.services" class="underline">ODIT.Services</a>
</strong> </strong>
<br /> <br />
<span class="text-lg">{$_('lfk-is-os')}</span> <span class="text-lg">{$_("lfk-is-os")}</span>
</p> </p>
</div> </div>
</div> </div>
@@ -110,82 +121,88 @@
<div class="pt-0 pb-16 overflow-hidden lg:pt-12 lg:py-24"> <div class="pt-0 pb-16 overflow-hidden lg:pt-12 lg:py-24">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<h2 class="text-4xl font-display font-semibold md:text-5xl"> <h2 class="text-4xl font-display font-semibold md:text-5xl">
{$_('credits')} {$_("credits")}
</h2> </h2>
<div class="max-w-3xl mx-auto text-xl leading-8 font-medium mt-8"> <div class="max-w-3xl mx-auto text-xl leading-8 font-medium mt-8">
<p class="text-center">{$_('oss_credit_description')}</p> <p class="text-center">{$_("oss_credit_description")}</p>
</div> </div>
<div class="w-screen leading-8 pl-5 mt-5"> <div class="w-screen leading-8 pl-5 mt-5">
{#await license_promise} {#await license_promise}
<p class="text-center w-full">{$_('licenses-are-being-loaded')}</p> <p class="text-center w-full">{$_("licenses-are-being-loaded")}</p>
{:then} {:then}
<table> <table>
<thead> <thead class="border-b border-gray-400">
<tr> <tr class="odd:bg-white even:bg-gray-100">
<th>{$_('dependency_name')}</th> <th>{$_("dependency_name")}</th>
<th>{$_('license')}</th> <th>{$_("license")}</th>
<th>{$_('repo_link')}</th> <th>{$_("repo_link")}</th>
<th>{$_('installed-version')}</th> <th>{$_("installed-version")}</th>
<th>{$_('author')}</th> <th>{$_("author")}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each licenses as l} {#each licenses as l}
<tr> <tr class="odd:bg-white even:bg-gray-100">
<td>{l.name}</td> <td>{l.name}</td>
<td> <td>
{l.license || '?'}<br /><span {l.license || "?"}<br /><button
class="underline cursor-pointer" class="underline cursor-pointer"
on:click={() => { on:click={() => {
modal_open = true; modal_open = true;
currentlicense = l.name + '@' + l.version; currentlicense = l.name + "@" + l.version;
licensetext = l.licensetext || $_('no-license-text-could-be-found'); licensetext =
}}>{$_('read-license')}</span> l.licensetext || $_("no-license-text-could-be-found");
}}>{$_("read-license")}</button
>
</td> </td>
<td> <td>
{(l.repo?.url || l.repo) {(l.repo?.url || l.repo)
.replace('git+', '') .replace("git+", "")
.replace('git://', '')} .replace("git://", "")}
</td> </td>
<td>{l.version || '?'}</td> <td>{l.version || "?"}</td>
<td>{l.author?.name || l.author || '?'}</td> <td>{l.author?.name || l.author || "?"}</td>
</tr> </tr>
{/each} {/each}
</tbody> </tbody>
</table> </table>
{:catch error} {:catch error}
<div <div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"
>
<span class="inline-block align-middle mr-8"> <span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b> <b class="capitalize">{$_("general_promise_error")}</b>
{error} {error}
</span> </span>
</div> </div>
{/await} {/await}
</div> </div>
<div class="w-full leading-8 mt-8"> <div class="w-full leading-8 mt-8">
<p class="text-xl font-medium">{$_('icon-image-credits')}</p> <p class="text-xl font-medium">{$_("icon-image-credits")}</p>
<ul class="list-disc"> <ul class="list-disc">
<li> <li>
<a <a
class="underline" class="underline"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="https://storyset.com">https://storyset.com</a> href="https://storyset.com">https://storyset.com</a
>
</li> </li>
<li> <li>
<a <a
class="underline" class="underline"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="https://undraw.co">https://undraw.co</a> href="https://undraw.co">https://undraw.co</a
>
</li> </li>
<li> <li>
<a <a
class="underline" class="underline"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="https://remixicon.com">https://remixicon.com</a> href="https://remixicon.com">https://remixicon.com</a
>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -20,27 +20,32 @@
class="underline" class="underline"
href="https://odit.services" href="https://odit.services"
rel="noopener,noreferrer" rel="noopener,noreferrer"
target="_blank">ODIT.Services</a> target="_blank">ODIT.Services</a
>
</p> </p>
<p class="text-sm text-gray-500 mt-4"> <p class="text-sm text-gray-500 mt-4">
<a <a
class="underline" class="underline"
target="_blank" target="_blank"
rel="noopener, noreferrer" rel="noopener, noreferrer"
href="https://git.odit.services/lfk/frontend/">LfK!Frontend</a>@<a href="https://git.odit.services/lfk/frontend/">LfK!Frontend</a
>@<a
class="underline" class="underline"
target="_blank" target="_blank"
rel="noopener, noreferrer" rel="noopener, noreferrer"
href="https://git.odit.services/lfk/frontend/src/tag/{releaseinfo}">{releaseinfo}</a> href="https://git.odit.services/lfk/frontend/src/tag/{releaseinfo}"
>{releaseinfo}</a
>
- -
<a <a
rel="noopener, noreferrer" rel="noopener, noreferrer"
class="underline" class="underline"
href="https://docs.lauf-fuer-kaya.de" href="https://docs.lauf-fuer-kaya.de"
target="_blank">{$_('documentation')}</a> target="_blank">{$_("documentation")}</a
>
- -
<a class="underline" href="/privacy">{$_('privacy')}</a> <a class="underline" href="/privacy">{$_("privacy")}</a>
- -
<a class="underline" href="/imprint">{$_('imprint')}</a> <a class="underline" href="/imprint">{$_("imprint")}</a>
</p> </p>
</footer> </footer>

View File

@@ -1,20 +1,20 @@
<script> <script>
import { _, getLocaleFromNavigator } from "svelte-i18n"; import { _, getLocaleFromNavigator } from "svelte-i18n";
import marked from "marked"; import { parse } from "marked";
import Footer from "./Footer.svelte"; import Footer from "./Footer.svelte";
import * as css from "../base/simple.css"; // import * as css from "../base/simple.css";
let html = ""; let html = "";
async function load() { async function load() {
let md = await fetch("/imprint_" + getLocaleFromNavigator() + ".md"); let md = await fetch("/imprint_" + getLocaleFromNavigator() + ".md");
let text = (await md.text()).toString(); let text = (await md.text()).toString();
if(text.includes("<meta")){ if (text.includes("<meta")) {
md.ok=false md.ok = false;
} }
if (!md.ok) { if (!md.ok) {
md = await fetch("/imprint_en.md"); md = await fetch("/imprint_en.md");
text = await md.text(); text = await md.text();
} }
html = marked(text); html = parse(text);
} }
const promise = load(); const promise = load();
</script> </script>
@@ -22,8 +22,9 @@
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12"> <div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8"> <div class="text-center mb-8">
<h1 <h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"> class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"
{$_('imprint')} >
{$_("imprint")}
</h1> </h1>
</div> </div>
</div> </div>
@@ -31,16 +32,17 @@
<div class="pt-0 pb-16 overflow-hidden lg:pt-12 lg:py-24"> <div class="pt-0 pb-16 overflow-hidden lg:pt-12 lg:py-24">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{#await promise} {#await promise}
<p class="text-center w-full">{$_('imprint-loading')}</p> <p class="text-center w-full">{$_("imprint-loading")}</p>
{:then} {:then}
<div class="simplecontent"> <div class="simplecontent">
{@html html} {@html html}
</div> </div>
{:catch error} {:catch error}
<div <div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"
>
<span class="inline-block align-middle mr-8"> <span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b> <b class="capitalize">{$_("general_promise_error")}</b>
{error} {error}
</span> </span>
</div> </div>

View File

@@ -4,19 +4,22 @@
<body class="antialiased font-sans"> <body class="antialiased font-sans">
<div class="flex min-h-screen"> <div class="flex min-h-screen">
<div class="w-full bg-white flex items-center justify-center "> <div class="w-full bg-white flex items-center justify-center">
<div class="max-w-sm m-8"> <div class="max-w-sm m-8">
<div class="text-black text-5xl md:text-15xl font-black"> <div class="text-black text-5xl md:text-15xl font-black">
{$_('404title')} {$_("404title")}
</div> </div>
<div class="w-16 h-1 bg-purple-light my-3 md:my-6" /> <div class="w-16 h-1 bg-purple-light my-3 md:my-6" />
<p <p
class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal"> class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal"
{$_('404message')} >
{$_("404message")}
</p> </p>
<a <a
href="/" href="/"
class="bg-transparent text-grey-darkest font-bold uppercase tracking-wide py-3 px-6 border-2 border-grey-light hover:border-grey rounded-lg">{$_('goback')}</a> class="bg-transparent text-grey-darkest font-bold uppercase tracking-wide py-3 px-6 border-2 border-grey-light hover:border-grey rounded-lg"
>{$_("goback")}</a
>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,20 +1,20 @@
<script> <script>
import { _, getLocaleFromNavigator } from "svelte-i18n"; import { _, getLocaleFromNavigator } from "svelte-i18n";
import marked from "marked"; import { parse } from "marked";
import Footer from "./Footer.svelte"; import Footer from "./Footer.svelte";
// import * as css from "../base/simple.css?inline"; // import * as css from "../base/simple.css?inline";
let html = ""; let html = "";
async function load() { async function load() {
let md = await fetch("/privacy_" + getLocaleFromNavigator() + ".md"); let md = await fetch("/privacy_" + getLocaleFromNavigator() + ".md");
let text = (await md.text()).toString(); let text = (await md.text()).toString();
if(text.includes("<meta")){ if (text.includes("<meta")) {
md.ok=false md.ok = false;
} }
if (!md.ok) { if (!md.ok) {
md = await fetch("/privacy_en.md"); md = await fetch("/privacy_en.md");
text = await md.text(); text = await md.text();
} }
html = marked(text); html = parse(text);
} }
const promise = load(); const promise = load();
</script> </script>
@@ -22,8 +22,9 @@
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12"> <div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8"> <div class="text-center mb-8">
<h1 <h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"> class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"
{$_('privacy')} >
{$_("privacy")}
</h1> </h1>
</div> </div>
</div> </div>
@@ -31,16 +32,17 @@
<div class="pt-0 pb-16 overflow-hidden lg:pt-12 lg:py-24"> <div class="pt-0 pb-16 overflow-hidden lg:pt-12 lg:py-24">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{#await promise} {#await promise}
<p class="text-center w-full">{$_('privacy-loading')}</p> <p class="text-center w-full">{$_("privacy-loading")}</p>
{:then} {:then}
<div class="simplecontent"> <div class="simplecontent">
{@html html} {@html html}
</div> </div>
{:catch error} {:catch error}
<div <div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"
>
<span class="inline-block align-middle mr-8"> <span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b> <b class="capitalize">{$_("general_promise_error")}</b>
{error} {error}
</span> </span>
</div> </div>

View File

@@ -4,26 +4,33 @@
<div class="md:flex flex-col md:flex-row h-screen w-full"> <div class="md:flex flex-col md:flex-row h-screen w-full">
<div <div
class="flex flex-col w-full md:w-64 text-gray-700 bg-white dark-mode:text-gray-200 dark-mode:bg-gray-800 flex-shrink-0"> class="flex flex-col w-full md:w-64 text-gray-700 bg-white dark-mode:text-gray-200 dark-mode:bg-gray-800 flex-shrink-0"
>
<div <div
class="flex-shrink-0 px-8 py-4 flex flex-row items-center justify-between"> class="flex-shrink-0 px-8 py-4 flex flex-row items-center justify-between"
>
<a <a
href="/#/test" href="/#/test"
class="text-lg font-semibold tracking-widest text-gray-900 uppercase rounded-lg dark-mode:text-white focus:outline-none focus:shadow-outline">Sidebar</a> class="text-lg font-semibold tracking-widest text-gray-900 uppercase rounded-lg dark-mode:text-white focus:outline-none focus:shadow-outline"
>Sidebar</a
>
<button <button
class="rounded-lg md:hidden focus:outline-none focus:shadow-outline"> class="rounded-lg md:hidden focus:outline-none focus:shadow-outline"
>
<svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6"> <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
{#if open} {#if open}
<path <path
fill-rule="evenodd" fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd" /> clip-rule="evenodd"
/>
{/if} {/if}
{#if !open} {#if !open}
<path <path
fill-rule="evenodd" fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z"
clip-rule="evenodd" /> clip-rule="evenodd"
/>
{/if} {/if}
</svg> </svg>
</button> </button>
@@ -31,49 +38,63 @@
<nav <nav
:class:block={open} :class:block={open}
:class:hidden={!open} :class:hidden={!open}
class="flex-grow md:block px-4 pb-4 md:pb-0 md:overflow-y-auto"> class="flex-grow md:block px-4 pb-4 md:pb-0 md:overflow-y-auto"
>
<a <a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-gray-200 rounded-lg dark-mode:bg-gray-700 dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline" class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-gray-200 rounded-lg dark-mode:bg-gray-700 dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Blog</a> href="#">Blog</a
>
<a <a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline" class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Portfolio</a> href="#">Portfolio</a
>
<a <a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline" class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">About</a> href="#">About</a
>
<a <a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline" class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Contact</a> href="#">Contact</a
>
<div class="relative"> <div class="relative">
<button <button
on:click={() => { on:click={() => {
open = !open; open = !open;
}} }}
class="flex flex-row items-center w-full px-4 py-2 mt-2 text-sm font-semibold text-left bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:focus:bg-gray-600 dark-mode:hover:bg-gray-600 md:block hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"> class="flex flex-row items-center w-full px-4 py-2 mt-2 text-sm font-semibold text-left bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:focus:bg-gray-600 dark-mode:hover:bg-gray-600 md:block hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
>
<span>Dropdown</span> <span>Dropdown</span>
<svg <svg
fill="currentColor" fill="currentColor"
viewBox="0 0 20 20" viewBox="0 0 20 20"
class="inline w-4 h-4 mt-1 ml-1 transition-transform duration-200 transform md:-mt-1"><path class="inline w-4 h-4 mt-1 ml-1 transition-transform duration-200 transform md:-mt-1"
><path
fill-rule="evenodd" fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd" /></svg> clip-rule="evenodd"
/></svg
>
</button> </button>
<div <div
class:block={open} class:block={open}
class:hidden={!open} class:hidden={!open}
class="absolute right-0 w-full mt-2 origin-top-right rounded-md shadow-lg"> class="absolute right-0 w-full mt-2 origin-top-right rounded-md shadow-lg"
>
<div <div
class="px-2 py-2 bg-white rounded-md shadow dark-mode:bg-gray-800"> class="px-2 py-2 bg-white rounded-md shadow dark-mode:bg-gray-800"
>
<a <a
class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline" class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Link #1</a> href="#">Link #1</a
>
<a <a
class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline" class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Link #2</a> href="#">Link #2</a
>
<a <a
class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline" class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Link #3</a> href="#">Link #3</a
>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,8 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import Toastify from "toastify-js";
import { UserGroupService } from "@odit/lfk-client-js"; import { UserGroupService } from "@odit/lfk-client-js";
import toast from "svelte-french-toast";
export let modal_open; export let modal_open;
export let current_groups; export let current_groups;
let description_input_value; let description_input_value;
@@ -32,10 +31,7 @@
function submit() { function submit() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ toast.loading($_("group-is-being-added"));
text: $_('group-is-being-added'),
duration: -1,
}).showToast();
let postdata = { let postdata = {
name: name_input_value, name: name_input_value,
description: description_input_value, description: description_input_value,
@@ -46,11 +42,8 @@
description_input_value = ""; description_input_value = "";
modal_open = false; modal_open = false;
// //
Toastify({ toast.dismiss();
text: $_('group-added'), toast.success($_("group-added"));
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_groups.push(result); current_groups.push(result);
current_groups = current_groups; current_groups = current_groups;
}) })
@@ -59,8 +52,6 @@
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
//
toast.hideToast();
}); });
} }
} }
@@ -69,83 +60,100 @@
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;
}}> }}
>
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" /> data-id="modal_backdrop"
/>
</div> </div>
<span <span
class="hidden sm:inline-block sm:align-middle sm:h-screen" class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span> aria-hidden="true">&#8203;</span
>
<div <div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline"> aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512" viewBox="0 0 640 512"
class="h-6 w-6 text-blue-600" class="h-6 w-6 text-blue-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"
/></svg
>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-user-group')} {$_("create-a-new-user-group")}
</h3> </h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_('please-provide-the-required-information-for-creating-a-new-user-group')} {$_(
"please-provide-the-required-information-for-creating-a-new-user-group"
)}
</p> </p>
</div> </div>
<div class="grid grid-cols-6 gap-6"> <div class="grid grid-cols-6 gap-6">
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="firstname" for="firstname"
class="block text-sm font-medium text-gray-700">{$_('name')}</label> class="block text-sm font-medium text-gray-700"
>{$_("name")}</label
>
<input <input
use:focus use:focus
autocomplete="off" autocomplete="off"
placeholder="{$_('name')}" placeholder={$_("name")}
class:border-red-500={!isNameValid} class:border-red-500={!isNameValid}
class:focus:border-red-500={!isNameValid} class:focus:border-red-500={!isNameValid}
class:focus:ring-red-500={!isNameValid} class:focus:ring-red-500={!isNameValid}
bind:value={name_input_value} bind:value={name_input_value}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isNameValid} {#if !isNameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('name-is-required')} >
{$_("name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="trackname" for="trackname"
class="block text-sm font-medium text-gray-700">{$_('description-optional')}</label> class="block text-sm font-medium text-gray-700"
>{$_("description-optional")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder="{$_('something-about-the-group')}" placeholder={$_("something-about-the-group")}
bind:value={description_input_value} bind:value={description_input_value}
type="text" type="text"
name="trackname" 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>
</div> </div>
</div> </div>
@@ -157,16 +165,18 @@
class:opacity-50={!createbtnenabled} class:opacity-50={!createbtnenabled}
on:click={submit} on:click={submit}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('create')} >
{$_("create")}
</button> </button>
<button <button
on:click={() => { on:click={() => {
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
{$_('cancel')} >
{$_("cancel")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,220 +1,238 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import store from "../../store"; import store from "../../store";
import { import { UserGroupService } from "@odit/lfk-client-js";
UserGroupService import toast from 'svelte-french-toast'
} from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import PromiseError from "../base/PromiseError.svelte";
import PromiseError from "../base/PromiseError.svelte"; let data_loaded = false;
let data_loaded = false; export let params;
export let params; const promise = UserGroupService.userGroupControllerGetOne(params.groupid);
const promise = UserGroupService.userGroupControllerGetOne(params.groupid); const colors = [
const colors = [ "#f3558e",
"#f3558e", "#17b978",
"#17b978", "#3498db",
"#3498db", "#3f3b3b",
"#3f3b3b", "#775ada",
"#775ada", "#7ed6df_#000000",
"#7ed6df_#000000", "#000000",
"#000000", "#21e6c1_#000000",
"#21e6c1_#000000", "#c0392b",
"#c0392b", "#d35400",
"#d35400", "#7f8c8d",
"#7f8c8d", "#6ab04c",
"#6ab04c", "#4834d4",
"#4834d4", "#ff1f5a",
"#ff1f5a", "#eac100",
"#eac100", ];
]; let matched_colors = [];
let matched_colors = []; $: delete_triggered = false;
$: delete_triggered = false; $: search_permission = "";
$: search_permission = ""; $: original_data = {};
$: original_data = {}; $: editable = {};
$: editable = {}; $: changes_performed = !(
$: changes_performed = !(JSON.stringify(original_data) == JSON.stringify(editable)); JSON.stringify(original_data) == JSON.stringify(editable)
$: isGroupnameValid = editable.name !== ""; );
$: save_enabled = $: isGroupnameValid = editable.name !== "";
changes_performed && isGroupnameValid $: save_enabled = changes_performed && isGroupnameValid;
promise.then((data) => { promise.then((data) => {
let current_target = ""; let current_target = "";
let colorindex = -1; let colorindex = -1;
data.permissions = data.permissions.sort(); data.permissions = data.permissions.sort();
data.permissions.forEach((p) => { data.permissions.forEach((p) => {
const target = p.split(":")[0]; const target = p.split(":")[0];
if (current_target !== p.split(":")[0]) { if (current_target !== p.split(":")[0]) {
colorindex++; colorindex++;
current_target = p.split(":")[0]; current_target = p.split(":")[0];
} }
let background = colors[colorindex]; let background = colors[colorindex];
let foreground = "#fff"; let foreground = "#fff";
if (background.includes("_")) { if (background.includes("_")) {
foreground = background.split("_")[1]; foreground = background.split("_")[1];
background = background.split("_")[0]; background = background.split("_")[0];
} }
matched_colors[target] = [background, foreground]; matched_colors[target] = [background, foreground];
}); });
data_loaded = true; data_loaded = true;
original_data = Object.assign(original_data, data); original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data); editable = Object.assign(editable, original_data);
}); });
function submit() { function submit() {
if (data_loaded === true && save_enabled) { if (data_loaded === true && save_enabled) {
Toastify({ toast($_("updating-group"));
text: $_('updateing-group'), UserGroupService.userGroupControllerPut(original_data.id, editable)
duration: 2500, .then((resp) => {
}).showToast(); Object.assign(original_data, editable);
UserGroupService.userGroupControllerPut(original_data.id, editable) original_data = editable;
.then((resp) => { Object.assign(original_data, editable);
Object.assign(original_data, editable); toast.success($_("group-updated"));
original_data = editable; })
Object.assign(original_data, editable); .catch((err) => {});
Toastify({ } else {
text: $_('group-updated'), }
duration: 2500, }
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", function deleteGroup() {
}).showToast(); UserGroupService.userGroupControllerRemove(original_data.id, true)
}) .then((resp) => {
.catch((err) => {}); location.replace("./");
} else { })
} .catch((err) => {});
} }
function deleteGroup() { </script>
UserGroupService.userGroupControllerRemove(original_data.id, true)
.then((resp) => { {#await promise}
location.replace("./"); {$_("loading-group-detail")}
}) {:then}
.catch((err) => {}); <section class="container p-5 select-none">
} <div class="flex flex-row mb-4">
</script> <div class="w-full">
<nav class="w-full flex">
{#await promise} <ol class="list-none flex flex-row items-center justify-start">
{$_('loading-group-detail')} <li class="flex items-center">
{:then} <svg
<section class="container p-5 select-none"> class="flex-shrink-0 w-5 h-5 mr-2"
<div class="flex flex-row mb-4"> fill="currentColor"
<div class="w-full"> width="24"
<nav class="w-full flex"> height="24"
<ol class="list-none flex flex-row items-center justify-start"> xmlns="http://www.w3.org/2000/svg"
<li class="flex items-center"> viewBox="0 0 640 512"
<svg class="flex-shrink-0 w-5 h-5 mr-2" fill="currentColor" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"></path></svg> ><path
</li> fill="currentColor"
<li class="flex items-center"> 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"
<a class="mr-2" href="../">{$_('groups')}</a><svg /></svg
stroke="currentColor" >
fill="none" </li>
stroke-width="2" <li class="flex items-center">
viewBox="0 0 24 24" <a class="mr-2" href="../">{$_("groups")}</a><svg
stroke-linecap="round" stroke="currentColor"
stroke-linejoin="round" fill="none"
class="h-3 w-3 mr-2 stroke-current" stroke-width="2"
height="1em" viewBox="0 0 24 24"
width="1em" stroke-linecap="round"
xmlns="http://www.w3.org/2000/svg"><line stroke-linejoin="round"
x1="5" class="h-3 w-3 mr-2 stroke-current"
y1="12" height="1em"
x2="19" width="1em"
y2="12" /> xmlns="http://www.w3.org/2000/svg"
<polyline points="12 5 19 12 12 19" /></svg> ><line x1="5" y1="12" x2="19" y2="12" />
</li> <polyline points="12 5 19 12 12 19" /></svg
<li class="flex items-center"> >
<span class="mr-2">{editable.name}</span> </li>
</li> <li class="flex items-center">
</ol> <span class="mr-2">{editable.name}</span>
</nav> </li>
</div> </ol>
</div> </nav>
<div class="mb-8 text-3xl font-extrabold leading-tight"> </div>
{original_data.name} </div>
<span data-id="group_actions_${editable.id}"> <div class="mb-8 text-3xl font-extrabold leading-tight">
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:DELETE')} {original_data.name}
{#if delete_triggered} <span data-id="group_actions_${editable.id}">
<button {#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:DELETE")}
on:click={deleteGroup} {#if delete_triggered}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button> <button
<button on:click={deleteGroup}
on:click={() => { 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:w-auto sm:text-sm"
delete_triggered = !delete_triggered; >{$_("confirm-deletion")}</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> <button
{/if} on:click={() => {
{#if !delete_triggered} delete_triggered = !delete_triggered;
<button }}
on:click={() => { 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"
delete_triggered = true; >{$_("cancel")}</button
}} >
type="button" {/if}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-group')}</button> {#if !delete_triggered}
{/if} <button
{/if} on:click={() => {
{#if !delete_triggered} delete_triggered = true;
<button }}
disabled={!save_enabled} type="button"
class:opacity-50={!save_enabled} 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:w-auto sm:text-sm"
type="button" >{$_("delete-group")}</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}
{/if} {/if}
</span> {#if !delete_triggered}
</div> <button
<!-- --> disabled={!save_enabled}
<div class="text-sm w-full"> class:opacity-50={!save_enabled}
<label type="button"
for="title" on:click={submit}
class="font-medium text-gray-700">{$_('name')}</label> 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:w-auto sm:text-sm"
<input >{$_("save-changes")}</button
autocomplete="off" >
placeholder={$_('name')} {/if}
type="text" </span>
bind:value={editable.name} </div>
class:border-red-500={!isGroupnameValid} <!-- -->
class:focus:border-red-500={!isGroupnameValid} <div class="text-sm w-full">
class:focus:ring-red-500={!isGroupnameValid} <label for="title" class="font-medium text-gray-700">{$_("name")}</label>
name="title" <input
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" /> autocomplete="off"
{#if !isGroupnameValid} placeholder={$_("name")}
<span type="text"
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> bind:value={editable.name}
{$_('group-name-is-required')} class:border-red-500={!isGroupnameValid}
</span> class:focus:border-red-500={!isGroupnameValid}
{/if} class:focus:ring-red-500={!isGroupnameValid}
</div> name="title"
<div class="text-sm w-full"> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
<label />
for="firstname" {#if !isGroupnameValid}
class="font-medium text-gray-700">{$_('description')}</label> <span
<input class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
autocomplete="off" >
placeholder={$_('description')} {$_("group-name-is-required")}
type="text" </span>
bind:value={editable.description} {/if}
name="firstname" </div>
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 class="text-sm w-full">
</div> <label for="firstname" class="font-medium text-gray-700"
<div class="text-sm w-full mt-8"> >{$_("description")}</label
<p class="font-medium mb-4"> >
{$_('permissions')} <input
<a autocomplete="off"
class="px-4 py-2 bg-gray-500 rounded-md text-white" placeholder={$_("description")}
href="/groups/{params.groupid}/permissions/">{$_('edit-permissions')}</a> type="text"
</p> bind:value={editable.description}
<div class="w-full sm:my-px sm:px-px sm:w-1/2"> name="firstname"
<input 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"
autocomplete="off" />
placeholder="{$_('search-for-permission')}" </div>
type="text" <div class="text-sm w-full mt-8">
bind:value={search_permission} <p class="font-medium mb-4">
class="mt-4 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" /> {$_("permissions")}
</div> <a
{#each original_data.permissions as p} class="px-4 py-2 bg-gray-500 rounded-md text-white"
{#if p.toLowerCase().includes(search_permission.toLowerCase())} href="/groups/{params.groupid}/permissions/"
<span >{$_("edit-permissions")}</a
style="background:{matched_colors[p.split(':')[0]][0]};color:{matched_colors[p.split(':')[0]][1]};" >
class="mt-1 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-indigo-100 rounded">{p}</span> </p>
<!-- --> <div class="w-full sm:my-px sm:px-px sm:w-1/2">
{/if} <input
{/each} autocomplete="off"
</div> placeholder={$_("search-for-permission")}
</section> type="text"
{:catch error} bind:value={search_permission}
<PromiseError {error} /> class="mt-4 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
{/await} />
</div>
{#each original_data.permissions as p}
{#if p.toLowerCase().includes(search_permission.toLowerCase())}
<span
style="background:{matched_colors[
p.split(':')[0]
][0]};color:{matched_colors[p.split(':')[0]][1]};"
class="mt-1 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-indigo-100 rounded"
>{p}</span
>
<!-- -->
{/if}
{/each}
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}

View File

@@ -3,9 +3,10 @@
import { import {
PermissionService, PermissionService,
CreatePermission, CreatePermission,
UserGroupService, UserGroupService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import toast from 'svelte-french-toast'
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
export let params; export let params;
let [ let [
@@ -20,15 +21,14 @@ UserGroupService,
$: save_enabled = $: save_enabled =
JSON.stringify(grantedPermissions) === JSON.stringify(grantedPermissions) ===
JSON.stringify(grantedPermissions_initial); JSON.stringify(grantedPermissions_initial);
const group_promise = UserGroupService.userGroupControllerGetOne(params.groupid); const group_promise = UserGroupService.userGroupControllerGetOne(
params.groupid
);
group_promise.then((data) => { group_promise.then((data) => {
original_data = Object.assign(original_data, data); original_data = Object.assign(original_data, data);
}); });
function submit() { function submit() {
Toastify({ toast.loading($_("updating-permissions"));
text: $_('updating-permissions'),
duration: 2500,
}).showToast();
to_delete.forEach((d) => { to_delete.forEach((d) => {
promises = promises.concat([ promises = promises.concat([
PermissionService.permissionControllerRemove(d, true), PermissionService.permissionControllerRemove(d, true),
@@ -50,11 +50,7 @@ UserGroupService,
); );
}); });
grantedPermissions_initial = grantedPermissions; grantedPermissions_initial = grantedPermissions;
Toastify({ toast($_("permissions-updated"));
text: $_("permissions-updated"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}); });
} }
Object.values(CreatePermission.target).forEach((t) => { Object.values(CreatePermission.target).forEach((t) => {
@@ -62,13 +58,15 @@ UserGroupService,
allpermissions = allpermissions.concat([{ target: t, action: a }]); allpermissions = allpermissions.concat([{ target: t, action: a }]);
}); });
}); });
UserGroupService.userGroupControllerGetPermissions(params.groupid).then((val) => { UserGroupService.userGroupControllerGetPermissions(params.groupid).then(
val.directlyGranted.forEach((p) => { (val) => {
delete p.responseType; val.directlyGranted.forEach((p) => {
grantedPermissions = grantedPermissions.concat([p]); delete p.responseType;
}); grantedPermissions = grantedPermissions.concat([p]);
grantedPermissions_initial = grantedPermissions; });
}); grantedPermissions_initial = grantedPermissions;
}
);
</script> </script>
{#await group_promise} {#await group_promise}
@@ -86,12 +84,15 @@ UserGroupService,
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path viewBox="0 0 640 512"
><path
fill="currentColor" fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"
/></svg
>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="../../">{$_('user-groups')}</a><svg <a class="mr-2" href="../../">{$_("user-groups")}</a><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
@@ -101,12 +102,10 @@ UserGroupService,
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"
x1="5" ><line x1="5" y1="12" x2="19" y2="12" />
y1="12" <polyline points="12 5 19 12 12 19" /></svg
x2="19" >
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="mr-2"><a href="../">{original_data.name}</a></span> <span class="mr-2"><a href="../">{original_data.name}</a></span>
@@ -122,22 +121,20 @@ UserGroupService,
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"
x1="5" ><line x1="5" y1="12" x2="19" y2="12" />
y1="12" <polyline points="12 5 19 12 12 19" /></svg
x2="19" >
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="mr-2">{$_('permissions')}</span> <span class="mr-2">{$_("permissions")}</span>
</li> </li>
</ol> </ol>
</nav> </nav>
</div> </div>
</div> </div>
<div class="mb-8 text-3xl font-extrabold"> <div class="mb-8 text-3xl font-extrabold">
{$_('permissions')}: {$_("permissions")}:
{original_data.name} {original_data.name}
<span> <span>
{#if promises.length === 0} {#if promises.length === 0}
@@ -146,21 +143,25 @@ UserGroupService,
class:opacity-50={save_enabled} class:opacity-50={save_enabled}
type="button" type="button"
on:click={submit} 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> 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:w-auto sm:text-sm"
>{$_("save-changes")}</button
>
{:else} {:else}
<button <button
type="button" type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-yellow-600 text-base font-medium text-white hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('applying-changes')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-yellow-600 text-base font-medium text-white hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 sm:w-auto sm:text-sm"
>{$_("applying-changes")}</button
>
{/if} {/if}
</span> </span>
</div> </div>
<!-- --> <!-- -->
<div class="flex flex-wrap -mx-1 overflow-hidden"> <div class="flex flex-wrap -mx-1 overflow-hidden">
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/2"> <div class="my-1 px-1 w-full overflow-hidden sm:w-1/2">
{$_('verfuegbare')} {$_("available-permissions")}
</div> </div>
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/2"> <div class="my-1 px-1 w-full overflow-hidden sm:w-1/2">
{$_('granted')} {$_("granted")}
</div> </div>
</div> </div>
<!-- --> <!-- -->
@@ -168,12 +169,14 @@ UserGroupService,
{#if allpermissions.length > 0} {#if allpermissions.length > 0}
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/2"> <div class="my-1 px-1 w-full overflow-hidden sm:w-1/2">
<div <div
class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center"> class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center"
>
{#each allpermissions as p} {#each allpermissions as p}
{#if !(grantedPermissions.filter((o)=>p.target == o.target && p.action == o.action).length > 0)} {#if !(grantedPermissions.filter((o) => p.target == o.target && p.action == o.action).length > 0)}
<p <p
class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input"> class="block w-full mt-1 text-sm bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
{p.target + ':' + p.action} >
{p.target + ":" + p.action}
<button <button
on:click={() => { on:click={() => {
grantedPermissions = grantedPermissions.concat([p]); grantedPermissions = grantedPermissions.concat([p]);
@@ -190,7 +193,9 @@ UserGroupService,
} }
}} }}
type="button" type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-200 text-base font-medium text-black hover:bg-green-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm">+</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-200 text-base font-medium text-black hover:bg-green-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:w-auto sm:text-sm"
>+</button
>
</p> </p>
{/if} {/if}
{/each} {/each}
@@ -198,22 +203,39 @@ UserGroupService,
</div> </div>
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/2"> <div class="my-1 px-1 w-full overflow-hidden sm:w-1/2">
<div <div
class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center"> class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center"
>
{#each grantedPermissions as p} {#each grantedPermissions as p}
<p <p
class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input"> class="block w-full mt-1 text-sm bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
{p.target + ':' + p.action} >
{p.target + ":" + p.action}
<button <button
on:click={() => { on:click={() => {
grantedPermissions = grantedPermissions.filter((o) => o.target + ':' + o.action !== p.target + ':' + p.action); grantedPermissions = grantedPermissions.filter(
if (to_add.some((o) => o.target + ':' + o.action === p.target + ':' + p.action)) { (o) =>
to_add = to_add.filter((o) => o.target + ':' + o.action !== p.target + ':' + p.action); o.target + ":" + o.action !== p.target + ":" + p.action
);
if (
to_add.some(
(o) =>
o.target + ":" + o.action ===
p.target + ":" + p.action
)
) {
to_add = to_add.filter(
(o) =>
o.target + ":" + o.action !==
p.target + ":" + p.action
);
} else { } else {
to_delete = to_delete.concat([p.id]); to_delete = to_delete.concat([p.id]);
} }
}} }}
type="button" type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-300 text-base font-medium text-black hover:bg-red-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm">-</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-300 text-base font-medium text-black hover:bg-red-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:w-auto sm:text-sm"
>-</button
>
</p> </p>
{/each} {/each}
</div> </div>

View File

@@ -8,22 +8,23 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_('user-groups')} {$_("user-groups")}
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:CREATE')} </h4>
<button {#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:CREATE")}
on:click={() => { <button
modal_open = true; 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"> type="button"
{$_('add-user-group')} 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:w-auto sm:text-sm"
</button> >
{/if} {$_("add-user-group")}
</span> </button>
{/if}
<UserGroupsOverview bind:current_groups /> <UserGroupsOverview bind:current_groups />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:CREATE")}
<AddGroupModal bind:current_groups bind:modal_open /> <AddGroupModal bind:current_groups bind:modal_open />
{/if} {/if}

View File

@@ -6,7 +6,7 @@
<div class="text-center items-center justify-center"> <div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500"> <p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={groups_empty} alt="" /> <img class="w-full h-44" src={groups_empty} alt="" />
<span class="font-bold">{$_('there-are-no-groups-yet')}.</span><br /> <span class="font-bold">{$_("there-are-no-groups-yet")}.</span><br />
<span>{$_('add-your-first-group')}</span> <span>{$_("add-your-first-group")}</span>
</p> </p>
</div> </div>

View File

@@ -13,13 +13,14 @@
); );
</script> </script>
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:GET")}
{#await groups_promise} {#await groups_promise}
<div <div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"> role="alert"
<p class="font-bold">{$_('groups-are-being-loaded')}</p> >
<p class="text-sm">{$_('this-might-take-a-moment')}</p> <p class="font-bold">{$_("groups-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:then} {:then}
{#if current_groups.length === 0} {#if current_groups.length === 0}
@@ -28,26 +29,30 @@
<input <input
type="search" type="search"
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_('datatable.search')} placeholder={$_("datatable.search")}
aria-label={$_('datatable.search')} aria-label={$_("datatable.search")}
class="mb-4" /> class="mb-2 w-full sm:w-auto mt-1 sm:mt-0 p-2 rounded-md border"
/>
<div <div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<table class="divide-y divide-gray-200 w-full"> <table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50"> <thead class="bg-gray-50">
<tr> <tr class="odd:bg-white even:bg-gray-100">
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('name')} >
{$_("name")}
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('description')} >
{$_("description")}
</th> </th>
<th scope="col" class="relative px-6 py-3"> <th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span> <span class="sr-only">{$_("action")}</span>
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -57,7 +62,10 @@
.toString() .toString()
.toLowerCase() .toLowerCase()
.includes(searchvalue)} .includes(searchvalue)}
<tr data-rowid="user_{group.id}"> <tr
class="odd:bg-white even:bg-gray-100"
data-rowid="user_{group.id}"
>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center"> <div class="flex items-center">
<div class="ml-4"> <div class="ml-4">
@@ -72,39 +80,53 @@
</td> </td>
{#if active_deletes[group.id] === true} {#if active_deletes[group.id] === true}
<td <td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button <button
on:click={() => { on:click={() => {
active_deletes[group.id] = false; active_deletes[group.id] = false;
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button <button
on:click={() => { on:click={() => {
UserGroupService.userGroupControllerRemove(group.id, true) UserGroupService.userGroupControllerRemove(
group.id,
true
)
.then((resp) => { .then((resp) => {
current_groups = current_groups.filter((obj) => obj.id !== group.id); current_groups = current_groups.filter(
(obj) => obj.id !== group.id
);
}) })
.catch((err) => { .catch((err) => {
// error deleting user // error deleting user
}); });
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
</td> </td>
{:else} {:else}
<td <td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a <a
href="./{group.id}" href="./{group.id}"
class="text-indigo-600 hover:text-indigo-900">Details</a> class="text-indigo-600 hover:text-indigo-900">Details</a
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:DELETE')} >
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:DELETE")}
<button <button
on:click={() => { on:click={() => {
active_deletes[group.id] = true; active_deletes[group.id] = true;
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if} {/if}
</td> </td>
{/if} {/if}
@@ -118,7 +140,7 @@
{:catch error} {:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> <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"> <span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b> <b class="capitalize">{$_("general_promise_error")}</b>
{error} {error}
</span> </span>
</div> </div>

View File

@@ -1,9 +1,10 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { RunnerOrganizationService } from "@odit/lfk-client-js"; import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import toast from "svelte-french-toast";
export let modal_open; export let modal_open;
export let current_organizations; export let current_organizations;
let name_input_dom; let name_input_dom;
@@ -48,10 +49,7 @@
function submit() { function submit() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ toast.loading($_("organization-is-being-added"));
text: $_("organization-is-being-added"),
duration: -1,
}).showToast();
let address = {}; let address = {};
if (address_checked === true) { if (address_checked === true) {
address = { address = {
@@ -70,17 +68,13 @@
.then((result) => { .then((result) => {
name = ""; name = "";
modal_open = false; modal_open = false;
Toastify({ toast.dismiss();
text: $_("organization-added"), toast.success($_("organization-added"));
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_organizations = current_organizations.concat([result]); current_organizations = current_organizations.concat([result]);
}) })
.catch((err) => {}) .catch((err) => {})
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
toast.hideToast();
}); });
} }
} }
@@ -89,57 +83,69 @@
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;
}}> }}
>
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" /> data-id="modal_backdrop"
/>
</div> </div>
<span <span
class="hidden sm:inline-block sm:align-middle sm:h-screen" class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span> aria-hidden="true">&#8203;</span
>
<div <div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline"> aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg <svg
class="h-6 w-6 text-blue-600" class="h-6 w-6 text-blue-600"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path height="24"
d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg> ><path
d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z"
/></svg
>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-organization')} {$_("create-a-new-organization")}
</h3> </h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_('please-provide-the-required-information-to-add-a-new-organization')} {$_(
"please-provide-the-required-information-to-add-a-new-organization"
)}
</p> </p>
</div> </div>
<div class="grid grid-cols-6 gap-6"> <div class="grid grid-cols-6 gap-6">
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="firstname" for="firstname"
class="block text-sm font-medium text-gray-700">{$_('name')}</label> class="block text-sm font-medium text-gray-700"
>{$_("name")}</label
>
<input <input
use:focus use:focus
autocomplete="off" autocomplete="off"
placeholder={$_('name')} placeholder={$_("name")}
class:border-red-500={!isOrgnameValid} class:border-red-500={!isOrgnameValid}
class:focus:border-red-500={!isOrgnameValid} class:focus:border-red-500={!isOrgnameValid}
class:focus:ring-red-500={!isOrgnameValid} class:focus:ring-red-500={!isOrgnameValid}
@@ -147,11 +153,13 @@
bind:this={name_input_dom} bind:this={name_input_dom}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isOrgnameValid} {#if !isOrgnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('organization-name-is-required')} >
{$_("organization-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
@@ -162,96 +170,112 @@
id="comments" id="comments"
name="comments" name="comments"
type="checkbox" type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label <label for="comments" class="font-medium text-gray-700"
for="comments" >{$_("address")}</label
class="font-medium text-gray-700">{$_('address')}</label> >
</div> </div>
</div> </div>
{#if address_checked === true} {#if address_checked === true}
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="address1" for="address1"
class="block text-sm font-medium text-gray-700">{$_('address')}</label> class="block text-sm font-medium text-gray-700"
<input >{$_("address")}</label
autocomplete="off" >
placeholder="{$_('address')}" <input
class:border-red-500={!isAddress1Valid} autocomplete="off"
class:focus:border-red-500={!isAddress1Valid} placeholder={$_("address")}
class:focus:ring-red-500={!isAddress1Valid} class:border-red-500={!isAddress1Valid}
bind:value={address_input1_value} class:focus:border-red-500={!isAddress1Valid}
bind:this={address_input1} class:focus:ring-red-500={!isAddress1Valid}
type="text" bind:value={address_input1_value}
name="address1" bind:this={address_input1}
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" /> type="text"
{#if !isAddress1Valid} name="address1"
<span 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="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> />
{$_('address-is-required')} {#if !isAddress1Valid}
</span> <span
{/if} class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
</div> >
<div class="col-span-6"> {$_("address-is-required")}
<label </span>
for="address2" {/if}
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label> </div>
<input <div class="col-span-6">
autocomplete="off" <label
placeholder="{$_('apartment-suite-etc')}" for="address2"
bind:value={address_input2_value} class="block text-sm font-medium text-gray-700"
bind:this={address_input2} >{$_("apartment-suite-etc")}</label
type="text" >
name="address2" <input
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" /> autocomplete="off"
</div> placeholder={$_("apartment-suite-etc")}
<div class="col-span-6"> bind:value={address_input2_value}
<label bind:this={address_input2}
for="zipcode" type="text"
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label> name="address2"
<input 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"
autocomplete="off" />
placeholder="{$_('zip-postal-code')}" </div>
class:border-red-500={!iszipcodevalid} <div class="col-span-6">
class:focus:border-red-500={!iszipcodevalid} <label
class:focus:ring-red-500={!iszipcodevalid} for="zipcode"
bind:value={address_zipcode_value} class="block text-sm font-medium text-gray-700"
bind:this={address_zipcode} >{$_("zip-postal-code")}</label
type="text" >
name="zipcode" <input
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" /> autocomplete="off"
{#if !iszipcodevalid} placeholder={$_("zip-postal-code")}
<span class:border-red-500={!iszipcodevalid}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class:focus:border-red-500={!iszipcodevalid}
{$_('valid-zipcode-postal-code-is-required')} class:focus:ring-red-500={!iszipcodevalid}
</span> bind:value={address_zipcode_value}
{/if} bind:this={address_zipcode}
</div> type="text"
<div class="col-span-6"> name="zipcode"
<label class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
for="city" />
class="block text-sm font-medium text-gray-700">{$_('city')}</label> {#if !iszipcodevalid}
<input <span
autocomplete="off" class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
placeholder="{$_('city')}" >
class:border-red-500={!iscityvalid} {$_("valid-zipcode-postal-code-is-required")}
class:focus:border-red-500={!iscityvalid} </span>
class:focus:ring-red-500={!iscityvalid} {/if}
bind:value={address_city_value} </div>
bind:this={address_city} <div class="col-span-6">
type="text" <label
name="city" for="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="block text-sm font-medium text-gray-700"
{#if !iscityvalid} >{$_("city")}</label
<span >
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> <input
{$_('valid-city-is-required')} autocomplete="off"
</span> placeholder={$_("city")}
{/if} class:border-red-500={!iscityvalid}
</div> class:focus:border-red-500={!iscityvalid}
{/if} class:focus:ring-red-500={!iscityvalid}
</div> bind:value={address_city_value}
bind:this={address_city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-city-is-required")}
</span>
{/if}
</div>
{/if}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -261,16 +285,18 @@
class:opacity-50={!createbtnenabled} class:opacity-50={!createbtnenabled}
on:click={submit} on:click={submit}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('create')} >
{$_("create")}
</button> </button>
<button <button
on:click={() => { on:click={() => {
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
{$_('cancel')} >
{$_("cancel")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,9 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { RunnerOrganizationService } from "@odit/lfk-client-js"; import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
export let modal_open; export let modal_open;
export let delete_org; export let delete_org;
@@ -18,11 +18,7 @@
true true
) )
.then((resp) => { .then((resp) => {
Toastify({ toast($_("organization-deleted"));
text: $_('organization-deleted'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./"); location.replace("./");
}) })
.catch((err) => {}); .catch((err) => {});
@@ -32,51 +28,59 @@
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside use:clickOutside
on:click_outside={cancelDelete}> on:click_outside={cancelDelete}
>
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" /> data-id="modal_backdrop"
/>
</div> </div>
<span <span
class="hidden sm:inline-block sm:align-middle sm:h-screen" class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span> aria-hidden="true">&#8203;</span
>
<div <div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline"> aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg <svg
class="h-6 w-6 text-blue-600" class="h-6 w-6 text-blue-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path viewBox="0 0 640 512"
><path
fill="currentColor" 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> 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
>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('attention')} {$_("attention")}
</h3> </h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_( {$_(
'do-you-want-to-delete-the-organization-delete_org-name', "do-you-want-to-delete-the-organization-delete_org-name",
{ {
values: { orgname: delete_org.name }, values: { orgname: delete_org.name },
} }
)}<br /> )}<br />
{$_('all-associated-teams-and-runners-will-be-deleted-too')} {$_("all-associated-teams-and-runners-will-be-deleted-too")}
</p> </p>
</div> </div>
</div> </div>
@@ -86,14 +90,16 @@
<button <button
on:click={deleteOrg} on:click={deleteOrg}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('confirm-delete-organization-and-associated-teams-runners')} >
{$_("confirm-delete-organization-and-associated-teams-runners")}
</button> </button>
<button <button
on:click={cancelDelete} on:click={cancelDelete}
type="button" type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
{$_('cancel-keep-organization')} >
{$_("cancel-keep-organization")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,507 +1,488 @@
<script> <script>
import { import {
GroupContactService, GroupContactService,
RunnerOrganizationService, RunnerOrganizationService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import Toastify from "toastify-js"; import toast from 'svelte-french-toast'
import store from "../../store";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte"; import store from "../../store";
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte"; import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
import PromiseError from "../base/PromiseError.svelte"; import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
import Select from "svelte-select"; import PromiseError from "../base/PromiseError.svelte";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import Select from "svelte-select";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import { tick } from "svelte"; import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
$: delete_triggered = false; import { tick } from "svelte";
$: address_valid_or_none = $: delete_triggered = false;
(isAddress1Valid && iszipcodevalid && iscityvalid) || $: address_valid_or_none =
editable.address_checked === false; (isAddress1Valid && iszipcodevalid && iscityvalid) ||
$: save_enabled = data_changed && address_valid_or_none; editable.address_checked === false;
let original = ""; $: save_enabled = data_changed && address_valid_or_none;
let original_object = {}; let original = "";
let contacts = []; let original_object = {};
let valueCopy = null; let contacts = [];
let areaDom; let valueCopy = null;
let copied = false; let areaDom;
export let params; let copied = false;
$: editable = {}; export let params;
$: contact = {}; $: editable = {};
$: data_loaded = false; $: contact = {};
$: data_changed = !(JSON.stringify(editable) === original); $: data_loaded = false;
$: isAddress1Valid = editable.address?.address1?.trim().length !== 0; $: data_changed = !(JSON.stringify(editable) === original);
$: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0; $: isAddress1Valid = editable.address?.address1?.trim().length !== 0;
$: iscityvalid = editable.address?.city?.trim().length !== 0; $: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0;
$: sponsoring_contracts_show = true; $: iscityvalid = editable.address?.city?.trim().length !== 0;
$: cards_show = true; $: sponsoring_contracts_show = true;
$: certificates_show = true; $: cards_show = true;
$: generate_orgs = [original_object]; $: certificates_show = true;
$: registrationLink = `${config.baseurl}/selfservice/register/${editable.registrationKey}`; $: generate_orgs = [original_object];
const getContactLabel = (option) => $: registrationLink = `${config.baseurl}/selfservice/register/${editable.registrationKey}`;
option.firstname + " " + (option.middlename || "") + " " + option.lastname; const getContactLabel = (option) =>
const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne( option.firstname + " " + (option.middlename || "") + " " + option.lastname;
params.orgid const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne(
).then((value) => { params.orgid
data_loaded = true; ).then((value) => {
value.address_checked = value.address.address1 !== null; data_loaded = true;
if (value.address_checked === false) { value.address_checked = value.address.address1 !== null;
value.address = { if (value.address_checked === false) {
address1: "", value.address = {
address2: "", address1: "",
city: "", address2: "",
postalcode: "", city: "",
country: "", postalcode: "",
}; country: "",
} };
editable = Object.assign(editable, value); }
editable = editable; editable = Object.assign(editable, value);
original_object = Object.assign(editable, value); editable = editable;
original = JSON.stringify(value); original_object = Object.assign(editable, value);
GroupContactService.groupContactControllerGetAll().then((val) => { original = JSON.stringify(value);
contacts = val.map((r) => { GroupContactService.groupContactControllerGetAll().then((val) => {
return { label: getContactLabel(r), value: r }; contacts = val.map((r) => {
}); return { label: getContactLabel(r), value: r };
if (editable.contact) { });
contact = contacts.find((g) => g.value.id == editable.contact.id); if (editable.contact) {
} else { contact = contacts.find((g) => g.value.id == editable.contact.id);
contact = null; } else {
} contact = null;
}); }
}); });
let modal_open = false; });
let delete_org = {}; let modal_open = false;
function deleteOrganization() { let delete_org = {};
RunnerOrganizationService.runnerOrganizationControllerRemove( function deleteOrganization() {
original_object.id, RunnerOrganizationService.runnerOrganizationControllerRemove(
false original_object.id,
) false
.then((resp) => { )
Toastify({ .then((resp) => {
text: $_("organization-deleted"), toast($_("organization-deleted"));
duration: 500, location.replace("./");
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", })
}).showToast(); .catch((err) => {
location.replace("./"); modal_open = true;
}) delete_org = original_object;
.catch((err) => { });
modal_open = true; }
delete_org = original_object; function submit() {
}); if (data_loaded === true && save_enabled) {
} toast($_("updating-organization"));
function submit() { let postdata = Object.assign({}, editable);
if (data_loaded === true && save_enabled) { if (postdata.address_checked === false) {
Toastify({ postdata.address = null;
text: $_("updating-organization"), }
duration: 2500, postdata.contact = postdata.contact?.id;
}).showToast(); RunnerOrganizationService.runnerOrganizationControllerPut(
let postdata = Object.assign({}, editable); original_object.id,
if (postdata.address_checked === false) { postdata
postdata.address = null; )
} .then((resp) => {
postdata.contact = postdata.contact?.id; editable.registrationKey = resp.registrationKey;
RunnerOrganizationService.runnerOrganizationControllerPut( original_object = Object.assign({}, editable);
original_object.id, original = JSON.stringify(original_object);
postdata toast.success($_("updated-organization"));
) })
.then((resp) => { .catch((err) => {});
editable.registrationKey = resp.registrationKey; } else {
original_object = Object.assign({}, editable); }
original = JSON.stringify(original_object); }
Toastify({ async function copy() {
text: $_("updated-organization"), if (!editable.registrationKey) {
duration: 2500, toast.error($_("you-have-to-save-your-changes-to-generate-a-link"));
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", return;
}).showToast(); }
}) valueCopy = registrationLink;
.catch((err) => {}); await tick();
} else { areaDom.focus();
} areaDom.select();
} try {
async function copy() { const successful = document.execCommand("copy");
if (!editable.registrationKey) { if (!successful) {
Toastify({ throw new Error();
text: $_("you-have-to-save-your-changes-to-generate-a-link"), }
duration: 500, toast($_("copied-link-to-clipboard"));
backgroundColor: copied = true;
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", } catch (err) {
}).showToast(); toast.error($_("error-whyile-copying-to-clipboard"));
return; }
} // we can notifi by event or storage about copy status
valueCopy = registrationLink; valueCopy = null;
await tick(); }
areaDom.focus(); export let import_modal_open = false;
areaDom.select(); </script>
try {
const successful = document.execCommand("copy"); {#if valueCopy != null}<textarea bind:this={areaDom}>{valueCopy}</textarea>{/if}
if (!successful) { <ImportRunnerModal
throw new Error(); on:cancelDelete={(event) => {
} import_modal_open = false;
Toastify({ }}
text: $_("copied-link-to-clipboard"), current_runners={[]}
duration: 500, passed_team={{}}
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", passed_orgs={[]}
}).showToast(); passed_org={editable}
copied = true; opened_from="OrgDetail"
} catch (err) { bind:import_modal_open
Toastify({ />
text: $_("error-whyile-copying-to-clipboard"), <ConfirmOrgDeletion bind:modal_open bind:delete_org />
duration: 500, {#if data_loaded}
backgroundColor: <section class="container p-5">
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", <div class="mb-8 text-3xl font-extrabold leading-tight">
}).showToast(); {original_object.name}
} <span data-id="org_actions_${editable.id}">
// we can notifi by event or storage about copy status <GenerateSponsoringContracts
valueCopy = null; bind:sponsoring_contracts_show
} bind:generate_orgs
export let import_modal_open = false; />
</script> <GenerateRunnerCards bind:cards_show bind:generate_orgs />
<GenerateRunnerCertificates bind:certificates_show bind:generate_orgs />
{#if valueCopy != null}<textarea bind:this={areaDom}>{valueCopy}</textarea>{/if} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:IMPORT")}
<ImportRunnerModal <button
on:cancelDelete={(event) => { on:click={() => {
import_modal_open = false; import_modal_open = true;
}} }}
current_runners={[]} type="button"
passed_team={{}} 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:w-auto sm:text-sm"
passed_orgs={[]} >
passed_org={editable} {$_("import-runners")}
opened_from="OrgDetail" </button>
bind:import_modal_open {/if}
/> {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:DELETE")}
<ConfirmOrgDeletion bind:modal_open bind:delete_org /> {#if delete_triggered}
{#if data_loaded} <button
<section class="container p-5"> on:click={deleteOrganization}
<div class="mb-8 text-3xl font-extrabold leading-tight"> 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:w-auto sm:text-sm"
{original_object.name} >{$_("confirm-delete")}</button
<span data-id="org_actions_${editable.id}"> >
<GenerateSponsoringContracts <button
bind:sponsoring_contracts_show on:click={() => {
bind:generate_orgs delete_triggered = !delete_triggered;
/> }}
<GenerateRunnerCards bind:cards_show bind:generate_orgs /> 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"
<GenerateRunnerCertificates bind:certificates_show bind:generate_orgs /> >{$_("cancel")}</button
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:IMPORT")} >
<button {/if}
on:click={() => { {#if !delete_triggered}
import_modal_open = true; <button
}} on:click={() => {
type="button" delete_triggered = true;
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm" }}
> type="button"
{$_("import-runners")} class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
</button> >{$_("delete-organization")}</button
{/if} >
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:DELETE")} {/if}
{#if delete_triggered} {/if}
<button {#if !delete_triggered}
on:click={deleteOrganization} <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" on:click={submit}
>{$_("confirm-delete")}</button disabled={!save_enabled}
> class:opacity-50={!save_enabled}
<button type="button"
on:click={() => { class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm"
delete_triggered = !delete_triggered; >{$_("save-changes")}</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" {/if}
>{$_("cancel")}</button </span>
> </div>
{/if} <div class="flex flex-row mb-4">
{#if !delete_triggered} <div class="w-full">
<button <nav class="w-full flex">
on:click={() => { <ol class="list-none flex flex-row items-center justify-start">
delete_triggered = true; <li class="mr-2 flex items-center">
}} <svg
type="button" stroke="currentColor"
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" fill="none"
>{$_("delete-organization")}</button stroke-width="2"
> viewBox="0 0 24 24"
{/if} stroke-linecap="round"
{/if} stroke-linejoin="round"
{#if !delete_triggered} class="h-3 w-3 stroke-current"
<button height="1em"
on:click={submit} width="1em"
disabled={!save_enabled} xmlns="http://www.w3.org/2000/svg"
class:opacity-50={!save_enabled} ><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
type="button" <polyline points="9 22 9 12 15 12 15 22" /></svg
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 </li>
> <li class="flex items-center">
{/if} <a class="mr-2" href="/">{$_("home")}</a><svg
</span> stroke="currentColor"
</div> fill="none"
<div class="flex flex-row mb-4"> stroke-width="2"
<div class="w-full"> viewBox="0 0 24 24"
<nav class="w-full flex"> stroke-linecap="round"
<ol class="list-none flex flex-row items-center justify-start"> stroke-linejoin="round"
<li class="mr-2 flex items-center"> class="h-3 w-3 mr-2 stroke-current"
<svg height="1em"
stroke="currentColor" width="1em"
fill="none" xmlns="http://www.w3.org/2000/svg"
stroke-width="2" ><line x1="5" y1="12" x2="19" y2="12" />
viewBox="0 0 24 24" <polyline points="12 5 19 12 12 19" /></svg
stroke-linecap="round" >
stroke-linejoin="round" </li>
class="h-3 w-3 stroke-current" <li class="mr-2 flex items-center">
height="1em" <svg
width="1em" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> width="24"
<polyline points="9 22 9 12 15 12 15 22" /></svg height="24"
> ><path fill="none" d="M0 0h24v24H0z" />
</li> <path
<li class="flex items-center"> 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"
<a class="mr-2" href="/">{$_("home")}</a><svg /></svg
stroke="currentColor" >
fill="none" </li>
stroke-width="2" <li class="flex items-center">
viewBox="0 0 24 24" <a class="mr-2" href="./">{$_("organizations")}</a><svg
stroke-linecap="round" stroke="currentColor"
stroke-linejoin="round" fill="none"
class="h-3 w-3 mr-2 stroke-current" stroke-width="2"
height="1em" viewBox="0 0 24 24"
width="1em" stroke-linecap="round"
xmlns="http://www.w3.org/2000/svg" stroke-linejoin="round"
><line x1="5" y1="12" x2="19" y2="12" /> class="h-3 w-3 mr-2 stroke-current"
<polyline points="12 5 19 12 12 19" /></svg height="1em"
> width="1em"
</li> xmlns="http://www.w3.org/2000/svg"
<li class="mr-2 flex items-center"> ><line x1="5" y1="12" x2="19" y2="12" />
<svg <polyline points="12 5 19 12 12 19" /></svg
xmlns="http://www.w3.org/2000/svg" >
viewBox="0 0 24 24" </li>
width="24" <li class="flex items-center">
height="24" <span class="mr-2">Org-Details #{params.orgid}</span>
><path fill="none" d="M0 0h24v24H0z" /> </li>
<path </ol>
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" </nav>
/></svg </div>
> </div>
</li> <div class="text-sm w-full">
<li class="flex items-center"> <label for="name" class="font-medium text-gray-700">{$_("name")}</label>
<a class="mr-2" href="./">{$_("organizations")}</a><svg <input
stroke="currentColor" autocomplete="off"
fill="none" placeholder={$_("name")}
stroke-width="2" type="text"
viewBox="0 0 24 24" bind:value={editable.name}
stroke-linecap="round" name="name"
stroke-linejoin="round" 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="h-3 w-3 mr-2 stroke-current" />
height="1em" </div>
width="1em" <div class="text-sm w-full">
xmlns="http://www.w3.org/2000/svg" <label for="contact" class="font-medium text-gray-700"
><line x1="5" y1="12" x2="19" y2="12" /> >{$_("contact")}</label
<polyline points="12 5 19 12 12 19" /></svg >
> <Select
</li> 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"
<li class="flex items-center"> itemFilter={(label, filterText, option) =>
<span class="mr-2">Org-Details #{params.orgid}</span> label.toLowerCase().includes(filterText.toLowerCase()) ||
</li> option.value.id.toString().startsWith(filterText.toLowerCase())}
</ol> items={contacts}
</nav> showChevron={true}
</div> placeholder={$_("no-contact-selected")}
</div> noOptionsMessage={$_("no-contact-found")}
<div class="text-sm w-full"> bind:selectedValue={contact}
<label for="name" class="font-medium text-gray-700">{$_("name")}</label> on:select={(selectedValue) =>
<input (editable.contact = selectedValue.detail.value)}
autocomplete="off" on:clear={() => (editable.contact = null)}
placeholder={$_("name")} />
type="text" </div>
bind:value={editable.name} <div>
name="name" <div class="flex items-start mt-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 class="flex items-center h-5">
/> <input
</div> bind:checked={editable.registrationEnabled}
<div class="text-sm w-full"> id="toggle_selfservice_feature"
<label for="contact" class="font-medium text-gray-700" name="toggle_selfservice_feature"
>{$_("contact")}</label type="checkbox"
> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
<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" </div>
itemFilter={(label, filterText, option) => <div class="ml-3 text-sm">
label.toLowerCase().includes(filterText.toLowerCase()) || <label
option.value.id.toString().startsWith(filterText.toLowerCase())} for="toggle_selfservice_feature"
items={contacts} class="font-medium text-gray-700"
showChevron={true} >{$_("selfservice-registration")}</label
placeholder={$_("no-contact-selected")} >
noOptionsMessage={$_("no-contact-found")} </div>
bind:selectedValue={contact} </div>
on:select={(selectedValue) => <div>
(editable.contact = selectedValue.detail.value)} {#if editable.registrationEnabled}
on:clear={() => (editable.contact = null)} <div class="text-sm w-full">
/> <button on:click={copy} class="inline-flex w-full">
</div> <p
<div> name="token"
<div class="flex items-start mt-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"
<div class="flex items-center h-5"> >
<input {#if editable.registrationKey}
bind:checked={editable.registrationEnabled} {registrationLink}
id="toggle_selfservice_feature" {:else}
name="toggle_selfservice_feature" {$_("you-have-to-save-your-changes-to-generate-a-link")}
type="checkbox" {/if}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" </p>
/> <div
</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"
<div class="ml-3 text-sm"> >
<label <svg
for="toggle_selfservice_feature" xmlns="http://www.w3.org/2000/svg"
class="font-medium text-gray-700" viewBox="0 0 24 24"
>{$_("selfservice-registration")}</label width="24"
> height="24"
</div> ><path fill="none" d="M0 0h24v24H0z" />
</div> <path
<div> fill="currentColor"
{#if editable.registrationEnabled} d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z"
<div class="text-sm w-full"> /></svg
<button on:click={copy} class="inline-flex w-full"> >
<p </div>
name="token" </button>
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}
> <p class="text-gray-500 text-xs">
{#if editable.registrationKey} {$_("click-to-copy-the-link-into-your-clipboard")}
{registrationLink} </p>
{:else} {/if}
{$_("you-have-to-save-your-changes-to-generate-a-link")} </div>
{/if} {/if}
</p> <!-- -->
<div <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" <div class="flex items-start mt-2">
> <div class="flex items-center h-5">
<svg <input
xmlns="http://www.w3.org/2000/svg" bind:checked={editable.address_checked}
viewBox="0 0 24 24" id="toggle_address_checkbox"
width="24" name="toggle_address_checkbox"
height="24" type="checkbox"
><path fill="none" d="M0 0h24v24H0z" /> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
<path />
fill="currentColor" </div>
d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" <div class="ml-3 text-sm">
/></svg <label
> for="toggle_address_checkbox"
</div> class="font-medium text-gray-700">{$_("address")}</label
</button> >
{#if editable.registrationKey} </div>
<p class="text-gray-500 text-xs"> </div>
{$_("click-to-copy-the-link-into-your-clipboard")} </div>
</p> {#if editable.address_checked === true}
{/if} <div class="col-span-6">
</div> <label
{/if} for="address1"
<!-- --> class="block text-sm font-medium text-gray-700"
<div> >{$_("address")}</label
<div class="flex items-start mt-2"> >
<div class="flex items-center h-5"> <input
<input autocomplete="off"
bind:checked={editable.address_checked} placeholder="Address"
id="toggle_address_checkbox" class:border-red-500={!isAddress1Valid}
name="toggle_address_checkbox" class:focus:border-red-500={!isAddress1Valid}
type="checkbox" class:focus:ring-red-500={!isAddress1Valid}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" bind:value={editable.address.address1}
/> type="text"
</div> name="address1"
<div class="ml-3 text-sm"> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
<label />
for="toggle_address_checkbox" {#if !isAddress1Valid}
class="font-medium text-gray-700">{$_("address")}</label <span
> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
</div> >
</div> {$_("address-is-required")}
</div> </span>
{#if editable.address_checked === true} {/if}
<div class="col-span-6"> </div>
<label <div class="col-span-6">
for="address1" <label
class="block text-sm font-medium text-gray-700" for="address2"
>{$_("address")}</label class="block text-sm font-medium text-gray-700"
> >{$_("apartment-suite-etc")}</label
<input >
autocomplete="off" <input
placeholder="Address" autocomplete="off"
class:border-red-500={!isAddress1Valid} placeholder={$_("apartment-suite-etc")}
class:focus:border-red-500={!isAddress1Valid} bind:value={editable.address.address2}
class:focus:ring-red-500={!isAddress1Valid} type="text"
bind:value={editable.address.address1} name="address2"
type="text" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
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" </div>
/> <div class="col-span-6">
{#if !isAddress1Valid} <label for="zipcode" class="block text-sm font-medium text-gray-700"
<span >{$_("zip-postal-code")}</label
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1" >
> <input
{$_("address-is-required")} autocomplete="off"
</span> placeholder={$_("zip-postal-code")}
{/if} class:border-red-500={!iszipcodevalid}
</div> class:focus:border-red-500={!iszipcodevalid}
<div class="col-span-6"> class:focus:ring-red-500={!iszipcodevalid}
<label bind:value={editable.address.postalcode}
for="address2" type="text"
class="block text-sm font-medium text-gray-700" name="zipcode"
>{$_("apartment-suite-etc")}</label class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
> />
<input {#if !iszipcodevalid}
autocomplete="off" <span
placeholder={$_("apartment-suite-etc")} class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
bind:value={editable.address.address2} >
type="text" {$_("valid-zipcode-postal-code-is-required")}
name="address2" </span>
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}
/> </div>
</div> <div class="col-span-6">
<div class="col-span-6"> <label for="city" class="block text-sm font-medium text-gray-700"
<label for="zipcode" class="block text-sm font-medium text-gray-700" >{$_("city")}</label
>{$_("zip-postal-code")}</label >
> <input
<input autocomplete="off"
autocomplete="off" placeholder={$_("city")}
placeholder={$_("zip-postal-code")} class:border-red-500={!iscityvalid}
class:border-red-500={!iszipcodevalid} class:focus:border-red-500={!iscityvalid}
class:focus:border-red-500={!iszipcodevalid} class:focus:ring-red-500={!iscityvalid}
class:focus:ring-red-500={!iszipcodevalid} bind:value={editable.address.city}
bind:value={editable.address.postalcode} type="text"
type="text" name="city"
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 !iscityvalid}
{#if !iszipcodevalid} <span
<span class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1" >
> {$_("valid-city-is-required")}
{$_("valid-zipcode-postal-code-is-required")} </span>
</span> {/if}
{/if} </div>
</div> {/if}
<div class="col-span-6"> <div class="text-sm w-full">
<label for="city" class="block text-sm font-medium text-gray-700" <span class="font-medium text-gray-700">{$_("distance")}</span>
>{$_("city")}</label <br />
> <span class="text-gray-700">{(original_object.total_distance / 1000).toFixed(2)} km</span>
<input </div>
autocomplete="off" </div>
placeholder={$_("city")} </div>
class:border-red-500={!iscityvalid} </section>
class:focus:border-red-500={!iscityvalid} {:else}
class:focus:ring-red-500={!iscityvalid} {#await promise}
bind:value={editable.address.city} {$_("organization-detail-is-being-loaded")}
type="text" {:catch error}
name="city" <PromiseError />
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" {/await}
/> {/if}
{#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>
</section>
{:else}
{#await promise}
{$_("organization-detail-is-being-loaded")}
{:catch error}
<PromiseError />
{/await}
{/if}

View File

@@ -1,219 +1,250 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
let modal_open = false; let modal_open = false;
let delete_org = {}; let delete_org = {};
import { RunnerOrganizationService } from "@odit/lfk-client-js"; import { RunnerOrganizationService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import OrgsEmptyState from "./OrgsEmptyState.svelte"; import OrgsEmptyState from "./OrgsEmptyState.svelte";
import Toastify from "toastify-js"; import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte"; import toast from "svelte-french-toast";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: sponsoring_contracts_show = current_organizations.some((r) => r.is_selected === true); $: sponsoring_contracts_show = current_organizations.some(
$: cards_show = current_organizations.some((r) => r.is_selected === true); (r) => r.is_selected === true
$: generate_orgs = current_organizations.filter((r) => r.is_selected === true); );
$: certificates_show = current_organizations.some( $: cards_show = current_organizations.some((r) => r.is_selected === true);
(r) => r.is_selected === true $: generate_orgs = current_organizations.filter(
); (r) => r.is_selected === true
export let current_organizations = []; );
$: certificates_show = current_organizations.some(
const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then( (r) => r.is_selected === true
(val) => { );
current_organizations = val; export let current_organizations = [];
}
); const promise =
</script> RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
<ConfirmOrgDeletion current_organizations = val;
on:cancelDelete={(event) => { }
modal_open = false; );
active_deletes[event.detail.id] = false; </script>
}}
bind:modal_open <ConfirmOrgDeletion
bind:delete_org /> on:cancelDelete={(event) => {
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')} modal_open = false;
{#await promise} active_deletes[event.detail.id] = false;
<div }}
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" bind:modal_open
role="alert"> bind:delete_org
<p class="font-bold">{$_('organizations-are-being-loaded')}</p> />
<p class="text-sm">{$_('this-might-take-a-moment')}</p> {#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:GET")}
</div> {#await promise}
{:then} <div
{#if current_organizations.length === 0} class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
<OrgsEmptyState /> role="alert"
{:else} >
<input <p class="font-bold">{$_("organizations-are-being-loaded")}</p>
type="search" <p class="text-sm">{$_("this-might-take-a-moment")}</p>
bind:value={searchvalue} </div>
placeholder={$_('datatable.search')} {:then}
aria-label={$_('datatable.search')} {#if current_organizations.length === 0}
class="mb-4" /> <OrgsEmptyState />
<div class="h-12"> {:else}
<GenerateSponsoringContracts <input
bind:sponsoring_contracts_show type="search"
bind:generate_orgs /> bind:value={searchvalue}
<GenerateRunnerCards placeholder={$_("datatable.search")}
bind:cards_show aria-label={$_("datatable.search")}
bind:generate_orgs /> class="mb-2 w-full sm:w-auto mt-1 sm:mt-0 p-2 rounded-md border"
<GenerateRunnerCertificates />
bind:certificates_show <div class="h-12">
bind:generate_orgs /> <GenerateSponsoringContracts
</div> bind:sponsoring_contracts_show
<div bind:generate_orgs
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> />
<table class="divide-y divide-gray-200 w-full"> <GenerateRunnerCards bind:cards_show bind:generate_orgs />
<thead class="bg-gray-50"> <GenerateRunnerCertificates bind:certificates_show bind:generate_orgs />
<tr> </div>
<th <div
scope="col" class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> >
<span <table class="divide-y divide-gray-200 w-full">
on:click={() => { <thead class="bg-gray-50">
const newstate = !current_organizations.some((r) => r.is_selected === true); <tr class="odd:bg-white even:bg-gray-100">
current_organizations = current_organizations.map((r) => { <th
r.is_selected = newstate; scope="col"
return r; class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
}); >
}} <button
class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)} on:click={() => {
{$_('deselect-all')} const newstate = !current_organizations.some(
{:else}{$_('select-all')}{/if} (r) => r.is_selected === true
</span> );
</th> current_organizations = current_organizations.map((r) => {
<th r.is_selected = newstate;
scope="col" return r;
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> });
{$_('name')} }}
</th> class="underline cursor-pointer select-none"
<th >{#if current_organizations.some((r) => r.is_selected === true)}
scope="col" {$_("deselect-all")}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> {:else}{$_("select-all")}{/if}
{$_('address')} </button>
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('contact')} >
</th> {$_("name")}
<th scope="col" class="relative px-6 py-3"> </th>
<span class="sr-only">{$_('action')}</span> <th
</th> scope="col"
</tr> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
</thead> >
<tbody class="divide-y divide-gray-200"> {$_("address")}
{#each current_organizations as o} </th>
{#if Object.values(o) <th
.toString() scope="col"
.toLowerCase() class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
.includes(searchvalue)} >
<tr data-rowid="org_{o.id}"> {$_("contact")}
<td class="px-6 py-4 whitespace-nowrap"> </th>
<input <th scope="col" class="relative px-6 py-3">
bind:checked={o.is_selected} <span class="sr-only">{$_("action")}</span>
type="checkbox" </th>
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> </tr>
</td> </thead>
<td class="px-6 py-4 whitespace-nowrap"> <tbody class="divide-y divide-gray-200">
<div class="flex items-center"> {#each current_organizations as o}
<div class="ml-4"> {#if Object.values(o)
<div class="text-sm font-medium text-gray-900"> .toString()
{o.name} .toLowerCase()
</div> .includes(searchvalue)}
</div> <tr
</div> class="odd:bg-white even:bg-gray-100"
</td> data-rowid="org_{o.id}"
<td class="px-6 py-4 whitespace-nowrap"> >
<div class="flex items-center"> <td class="px-6 py-4 whitespace-nowrap">
<div class="ml-4"> <input
<div class="text-sm font-medium text-gray-900"> bind:checked={o.is_selected}
{#if o.address.address1 !== null} type="checkbox"
{o.address.address1}<br /> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
<!-- {o.address.address2 || ''}<br /> --> />
{o.address.postalcode} </td>
{o.address.city} <td class="px-6 py-4 whitespace-nowrap">
{o.address.country} <div class="flex items-center">
{/if} <div class="ml-4">
</div> <div class="text-sm font-medium text-gray-900">
</div> {o.name}
</div> </div>
</td> </div>
<td class="px-6 py-4 whitespace-nowrap"> </div>
<div class="flex items-center"> </td>
<div class="ml-4"> <td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900"> <div class="flex items-center">
{#if o.contact} <div class="ml-4">
<a <div class="text-sm font-medium text-gray-900">
href="../contacts/{o.contact.id}" {#if o.address.address1 !== null}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname} {o.address.address1}<br />
{o.contact.middlename || ''} <!-- {o.address.address2 || ''}<br /> -->
{o.contact.lastname}</a> {o.address.postalcode}
{:else}{$_('no-contact-specified')}{/if} {o.address.city}
</div> {o.address.country}
</div> {/if}
</div> </div>
</td> </div>
{#if active_deletes[o.id] === true} </div>
<td </td>
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <td class="px-6 py-4 whitespace-nowrap">
<button <div class="flex items-center">
on:click={() => { <div class="ml-4">
active_deletes[o.id] = false; <div class="text-sm font-medium text-gray-900">
}} {#if o.contact}
tabindex="0" <a
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> href="../contacts/{o.contact.id}"
<button class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
on:click={() => { >{o.contact.firstname}
RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false) {o.contact.middlename || ""}
.then((resp) => { {o.contact.lastname}</a
current_organizations = current_organizations.filter((obj) => obj.id !== o.id); >
Toastify({ {:else}{$_("no-contact-specified")}{/if}
text: 'Organization deleted', </div>
duration: 500, </div>
backgroundColor: </div>
'linear-gradient(to right, #00b09b, #96c93d)', </td>
}).showToast(); {#if active_deletes[o.id] === true}
}) <td
.catch((err) => { class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
modal_open = true; >
delete_org = o; <button
}); on:click={() => {
}} active_deletes[o.id] = false;
tabindex="0" }}
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> tabindex="0"
</td> class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
{:else} >{$_("cancel-delete")}</button
<td >
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <button
<a on:click={() => {
href="./{o.id}" toast.loading($_("deleting-organization"));
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> RunnerOrganizationService.runnerOrganizationControllerRemove(
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')} o.id,
<button false
on:click={() => { )
active_deletes[o.id] = true; .then((resp) => {
}} current_organizations =
tabindex="0" current_organizations.filter(
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> (obj) => obj.id !== o.id
{/if} );
</td> toast($_("organization-deleted"));
{/if} })
</tr> .catch((err) => {
{/if} modal_open = true;
{/each} delete_org = o;
</tbody> });
</table> }}
</div> tabindex="0"
{/if} class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
{:catch error} >{$_("confirm-delete")}</button
<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"> </td>
<b class="capitalize">{$_('general_promise_error')}</b> {:else}
{error} <td
</span> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
</div> >
{/await} <a
{/if} 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

@@ -10,33 +10,35 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_('organizations')} {$_("organizations")}
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:CREATE')} </h4>
<button {#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:CREATE")}
on:click={() => { <button
modal_open = true; 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"> type="button"
{$_('create-organization')} 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:w-auto sm:text-sm mt-1 sm:mt-0"
</button> >
{/if} {$_("create-organization")}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} </button>
<button {/if}
on:click={() => { {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:IMPORT")}
import_modal_open = true; <button
}} on:click={() => {
type="button" import_modal_open = true;
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')} type="button"
</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:w-auto sm:text-sm mt-1 sm:mt-0"
{/if} >
</span> {$_("import-runners")}
</button>
{/if}
<OrgOverview bind:current_organizations /> <OrgOverview bind:current_organizations />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:CREATE")}
<AddOrgModal bind:current_organizations bind:modal_open /> <AddOrgModal bind:current_organizations bind:modal_open />
<ImportRunnerModal <ImportRunnerModal
on:cancelDelete={(event) => { on:cancelDelete={(event) => {
@@ -47,5 +49,6 @@
passed_orgs={current_organizations} passed_orgs={current_organizations}
opened_from="OrgOverview" opened_from="OrgOverview"
current_runners={[]} current_runners={[]}
bind:import_modal_open /> bind:import_modal_open
/>
{/if} {/if}

View File

@@ -9,9 +9,9 @@
<div class="text-center items-center justify-center"> <div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500"> <p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={org_empty} alt="" /> <img class="w-full h-44" src={org_empty} alt="" />
<span <span class="font-bold">{$_("there-are-no-organizations-added-yet")}</span
class="font-bold">{$_('there-are-no-organizations-added-yet')}</span><br /> ><br />
<span>{$_('add-your-first-organization')}</span> <span>{$_("add-your-first-organization")}</span>
</p> </p>
</div> </div>

View File

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

@@ -1,355 +1,312 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { import {
DonationService, DonationService,
RunnerTeamService, RunnerTeamService,
RunnerOrganizationService, RunnerOrganizationService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import { init } from "@paralleldrive/cuid2";
import { init } from "@paralleldrive/cuid2"; import toast from "svelte-french-toast";
const createId = init({ length: 10, fingerprint: "lfk-frontend" }); const createId = init({ length: 10, fingerprint: "lfk-frontend" });
export let certificates_show = false; export let certificates_show = false;
export let generate_runners = []; export let generate_runners = [];
export let generate_orgs = []; export let generate_orgs = [];
export let generate_teams = []; export let generate_teams = [];
$: certificates_dropdown_open = false; $: certificates_dropdown_open = false;
document.addEventListener("click", function (e) { document.addEventListener("click", function (e) {
if ( if (
e.target.parentNode?.parentNode?.id != "certificates:dropdown" && e.target.parentNode?.parentNode?.id != "certificates:dropdown" &&
e.target.parentNode?.parentNode?.id != "certificates:dropdown:menu" e.target.parentNode?.parentNode?.id != "certificates:dropdown:menu"
) { ) {
certificates_dropdown_open = false; certificates_dropdown_open = false;
} }
}); });
function generateCertificates(locale) { function generateCertificates(locale) {
certificates_dropdown_open = false; certificates_dropdown_open = false;
if (generate_orgs.length > 0) { if (generate_orgs.length > 0) {
generateOrgCertificates(locale); generateOrgCertificates(locale);
} else if (generate_teams.length > 0) { } else if (generate_teams.length > 0) {
generateTeamCertificates(locale); generateTeamCertificates(locale);
} else { } else {
generateRunnerCertificates(locale); generateRunnerCertificates(locale);
} }
} }
async function generateRunnerCertificates(locale) { async function generateRunnerCertificates(locale) {
const toast = Toastify({ toast.loading($_("generating-pdf"));
text: $_("generating-pdf"), const current_donations =
duration: -1, (await DonationService.donationControllerGetAll()) || [];
}).showToast(); let certificateRunners = [];
const current_donations = for (let runner of generate_runners) {
(await DonationService.donationControllerGetAll()) || []; runner.distanceDonations =
let certificateRunners = []; current_donations.filter((d) => d.runner?.id == runner.id) || [];
for (let runner of generate_runners) { certificateRunners.push(runner);
runner.distanceDonations = }
current_donations.filter((d) => d.runner?.id == runner.id) || []; fetch(
console.log(runner.distanceDonations); `${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
certificateRunners.push(runner); {
} method: "POST",
fetch( headers: {
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`, "Content-Type": "application/json",
{ },
method: "POST", body: JSON.stringify(certificateRunners),
headers: { }
"Content-Type": "application/json", )
}, .then((response) => {
body: JSON.stringify(certificateRunners), if (response.status != "200") {
} toast.dismiss();
) toast.error($_("pdf-generation-failed"));
.then((response) => { } else {
if (response.status != "200") { return response.blob();
toast.hideToast(); }
Toastify({ })
text: $_("pdf-generation-failed"), .then((blob) => {
duration: 3500, const url = window.URL.createObjectURL(blob);
backgroundColor: let a = document.createElement("a");
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", a.href = url;
}).showToast(); if (generate_runners.length == 1) {
} else { a.download = `${$_("certificates")}_${
return response.blob(); generate_runners[0].firstname
} }_${generate_runners[0].lastname}-${locale}-${createId()}.pdf`;
}) } else {
.then((blob) => { a.download = `${$_("certificates")}-${locale}.pdf`;
const url = window.URL.createObjectURL(blob); }
let a = document.createElement("a"); document.body.appendChild(a);
a.href = url; a.click();
if (generate_runners.length == 1) { a.remove();
a.download = `${$_("certificates")}_${ toast.dismiss();
generate_runners[0].firstname toast($_("pdf-successfully-generated"));
}_${generate_runners[0].lastname}-${locale}-${createId()}.pdf`; })
} else { .catch((err) => {});
a.download = `${$_("certificates")}-${locale}.pdf`; }
}
document.body.appendChild(a); async function generateTeamCertificates(locale) {
a.click(); toast.loading($_("generating-pdfs"));
a.remove(); let count = 0;
toast.hideToast(); const current_donations =
Toastify({ (await DonationService.donationControllerGetAll()) || [];
text: $_("pdf-successfully-generated"), for (const t of generate_teams) {
duration: 3500, const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", t.id
}).showToast(); );
}) let certificateRunners = [];
.catch((err) => {}); for (let runner of runners) {
} runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || [];
async function generateTeamCertificates(locale) { certificateRunners.push(runner);
const toast = Toastify({ }
text: $_("generating-pdfs"), fetch(
duration: -1, `${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
}).showToast(); {
let count = 0; method: "POST",
const current_donations = headers: {
(await DonationService.donationControllerGetAll()) || []; "Content-Type": "application/json",
for (const t of generate_teams) { },
const runners = await RunnerTeamService.runnerTeamControllerGetRunners( body: JSON.stringify(certificateRunners),
t.id }
); )
let certificateRunners = []; .then((response) => {
for (let runner of runners) { if (response.status != "200") {
runner.distanceDonations = toast.dismiss();
current_donations.filter((d) => d.runner?.id == runner.id) || []; toast.error($_("pdf-generation-failed"));
certificateRunners.push(runner); } else {
} return response.blob();
fetch( }
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`, })
{ .then((blob) => {
method: "POST", count++;
headers: { const url = window.URL.createObjectURL(blob);
"Content-Type": "application/json", let a = document.createElement("a");
}, a.href = url;
body: JSON.stringify(certificateRunners), a.download = `${$_("certificates")}_${
} t.name
) }-${locale}-${createId()}.pdf`;
.then((response) => { document.body.appendChild(a);
if (response.status != "200") { a.click();
toast.hideToast(); a.remove();
Toastify({ if (count === generate_teams.length) {
text: $_("pdf-generation-failed"), toast.dismiss();
duration: 3500, toast.success($_("pdfs-successfully-generated"));
backgroundColor: }
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", })
}).showToast(); .catch((err) => {});
} else { }
return response.blob(); }
}
}) async function generateOrgCertificates(locale) {
.then((blob) => { toast.loading($_("generating-pdfs"));
count++; const current_donations =
const url = window.URL.createObjectURL(blob); (await DonationService.donationControllerGetAll()) || [];
let a = document.createElement("a"); let count = 0;
a.href = url; let count_orgs = 0;
a.download = `${$_("certificates")}_${t.name}-${locale}-${createId()}.pdf`; for (const o of generate_orgs) {
document.body.appendChild(a); count_orgs++;
a.click(); let count = 0;
a.remove(); let runners =
if (count === generate_teams.length) { await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
toast.hideToast(); o.id,
Toastify({ true
text: $_("pdfs-successfully-generated"), );
duration: 3500, let certificateRunners = [];
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", for (let runner of runners) {
}).showToast(); runner.distanceDonations =
} current_donations.filter((d) => d.runner?.id == runner.id) || [];
}) certificateRunners.push(runner);
.catch((err) => {}); }
} await fetch(
} `${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
async function generateOrgCertificates(locale) { method: "POST",
const toast = Toastify({ headers: {
text: $_("generating-pdfs"), "Content-Type": "application/json",
duration: -1, },
}).showToast(); body: JSON.stringify(certificateRunners),
const current_donations = }
(await DonationService.donationControllerGetAll()) || []; )
let count = 0; .then((response) => {
let count_orgs = 0; if (response.status != "200") {
for (const o of generate_orgs) { toast.dismiss();
count_orgs++; toast.error($_("pdf-generation-failed"));
let count = 0; } else {
let runners = return response.blob();
await RunnerOrganizationService.runnerOrganizationControllerGetRunners( }
o.id, })
true .then((blob) => {
); const url = window.URL.createObjectURL(blob);
let certificateRunners = []; let a = document.createElement("a");
for (let runner of runners) { a.href = url;
runner.distanceDonations = a.download = `${$_("certificates")}_${
current_donations.filter((d) => d.runner?.id == runner.id) || []; o.name
certificateRunners.push(runner); }-${locale}-${createId()}.pdf`;
} document.body.appendChild(a);
await fetch( a.click();
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`, a.remove();
{ if (count === o.teams.length && count_orgs === generate_orgs.length) {
method: "POST", toast.dismiss();
headers: { toast.success($_("pdfs-successfully-generated"));
"Content-Type": "application/json", }
}, })
body: JSON.stringify(certificateRunners), .catch((err) => {});
} for (const t of o.teams) {
) count++;
.then((response) => { let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
if (response.status != "200") { t.id
toast.hideToast(); );
Toastify({ let certificateRunners = [];
text: $_("pdf-generation-failed"), for (let runner of runners) {
duration: 3500, runner.distanceDonations =
backgroundColor: current_donations.filter((d) => d.runner?.id == runner.id) || [];
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", certificateRunners.push(runner);
}).showToast(); }
} else { await fetch(
return response.blob(); `${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
} {
}) method: "POST",
.then((blob) => { headers: {
const url = window.URL.createObjectURL(blob); "Content-Type": "application/json",
let a = document.createElement("a"); },
a.href = url; body: JSON.stringify(certificateRunners),
a.download = `${$_("certificates")}_${o.name}-${locale}-${createId()}.pdf`; }
document.body.appendChild(a); )
a.click(); .then((response) => {
a.remove(); if (response.status != "200") {
if (count === o.teams.length && count_orgs === generate_orgs.length) { toast.dismiss();
toast.hideToast(); toast.error($_("pdf-generation-failed"));
console.log("here"); } else {
Toastify({ return response.blob();
text: $_("pdfs-successfully-generated"), }
duration: 3500, })
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", .then((blob) => {
}).showToast(); const url = window.URL.createObjectURL(blob);
} let a = document.createElement("a");
}) a.href = url;
.catch((err) => {}); a.download = `${$_("certificates")}_${o.name}_${
for (const t of o.teams) { t.name
count++; }-${locale}-${createId()}.pdf`;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners( document.body.appendChild(a);
t.id a.click();
); a.remove();
let certificateRunners = []; if (
for (let runner of runners) { count === o.teams.length &&
runner.distanceDonations = count_orgs === generate_orgs.length
current_donations.filter((d) => d.runner?.id == runner.id) || []; ) {
certificateRunners.push(runner); toast.dismiss();
} toast($_("pdfs-successfully-generated"));
await fetch( }
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`, })
{ .catch((err) => {});
method: "POST", }
headers: { }
"Content-Type": "application/json", }
}, </script>
body: JSON.stringify(certificateRunners),
} {#if certificates_show}
) <div id="certificates:dropdown" class="relative inline-block">
.then((response) => { <div>
if (response.status != "200") { <button
toast.hideToast(); on:click={() => {
Toastify({ certificates_dropdown_open = !certificates_dropdown_open;
text: $_("pdf-generation-failed"), }}
duration: 3500, type="button"
backgroundColor: 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"
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", id="options-menu"
}).showToast(); aria-haspopup="true"
} else { aria-expanded="true"
return response.blob(); >
} {$_("generate-runner-certificates")}
}) <svg
.then((blob) => { xmlns="http://www.w3.org/2000/svg"
const url = window.URL.createObjectURL(blob); width="24"
let a = document.createElement("a"); height="24"
a.href = url; viewBox="0 0 24 24"
a.download = `${$_("certificates")}_${o.name}_${ class="-mr-1 ml-2 h-5 w-5"
t.name ><path fill="none" d="M0 0h24v24H0z" />
}-${locale}-${createId()}.pdf`; <path
document.body.appendChild(a); fill="currentColor"
a.click(); d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"
a.remove(); /></svg
if ( >
count === o.teams.length && </button>
count_orgs === generate_orgs.length </div>
) { {#if certificates_dropdown_open}
toast.hideToast(); <div
Toastify({ 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"
text: $_("pdfs-successfully-generated"), id="certificates:dropdown:menu"
duration: 3500, >
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", <div
}).showToast(); class="py-1"
} role="menu"
}) aria-orientation="vertical"
.catch((err) => {}); aria-labelledby="options-menu"
} >
} <span class="block w-full text-left px-4 py-2 text-sm text-gray-700"
} >{$_("select-language")}</span
</script> >
<button
{#if certificates_show} on:click={() => {
<div id="certificates:dropdown" class="relative inline-block"> generateCertificates("de");
<div> }}
<button type="submit"
on:click={() => { 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"
certificates_dropdown_open = !certificates_dropdown_open; role="menuitem"
}} >
type="button" {$_("german")}
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" </button>
id="options-menu" <button
aria-haspopup="true" on:click={() => {
aria-expanded="true" generateCertificates("en");
> }}
{$_("generate-runner-certificates")} type="submit"
<svg 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"
xmlns="http://www.w3.org/2000/svg" role="menuitem"
width="24" >
height="24" {$_("english")}
viewBox="0 0 24 24" </button>
class="-mr-1 ml-2 h-5 w-5" </div>
><path fill="none" d="M0 0h24v24H0z" /> </div>
<path {/if}
fill="currentColor" </div>
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" {/if}
/></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

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

@@ -3,78 +3,90 @@
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div> </div>
<div class="relative rounded-full w-8 h-8"> <div class="relative rounded-full w-8 h-8">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div> </div>
<div class="relative rounded-full w-12 h-12"> <div class="relative rounded-full w-12 h-12">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div> </div>
<div class="relative rounded-full w-16 h-16"> <div class="relative rounded-full w-16 h-16">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div> </div>
<div class="relative rounded-full w-20 h-20"> <div class="relative rounded-full w-20 h-20">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div> </div>
<div class="relative rounded-full w-24 h-24"> <div class="relative rounded-full w-24 h-24">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div> </div>
<h3 class="text-lg">Status Avatars</h3> <h3 class="text-lg">Status Avatars</h3>
<div class="relative rounded-full w-4 h-4"> <div class="relative rounded-full w-4 h-4">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
<div class="absolute rounded-full right-0 bottom-0 w-1 h-1 bg-gray-200" /> <div class="absolute rounded-full right-0 bottom-0 w-1 h-1 bg-gray-200" />
</div> </div>
<div class="relative rounded-full w-8 h-8"> <div class="relative rounded-full w-8 h-8">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
<div class="absolute rounded-full right-0 bottom-0 w-2 h-2 bg-green-400" /> <div class="absolute rounded-full right-0 bottom-0 w-2 h-2 bg-green-400" />
</div> </div>
<div class="relative rounded-full w-12 h-12"> <div class="relative rounded-full w-12 h-12">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
<div class="absolute rounded-full right-0 bottom-0 w-4 h-4 bg-red-600" /> <div class="absolute rounded-full right-0 bottom-0 w-4 h-4 bg-red-600" />
</div> </div>
<div class="relative rounded-full w-16 h-16"> <div class="relative rounded-full w-16 h-16">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
<div class="absolute rounded-full right-0 bottom-0 w-5 h-5 bg-gray-200" /> <div class="absolute rounded-full right-0 bottom-0 w-5 h-5 bg-gray-200" />
</div> </div>
<div class="relative rounded-full w-20 h-20"> <div class="relative rounded-full w-20 h-20">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
<div class="absolute rounded-full right-0 bottom-0 w-6 h-6 bg-green-400" /> <div class="absolute rounded-full right-0 bottom-0 w-6 h-6 bg-green-400" />
</div> </div>
<div class="relative rounded-full w-24 h-24"> <div class="relative rounded-full w-24 h-24">
<img <img
alt="" alt=""
src="https://gustui.s3.amazonaws.com/avatar.png" src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" /> class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
<div class="absolute rounded-full right-0 bottom-0 w-6 h-6 bg-red-600" /> <div class="absolute rounded-full right-0 bottom-0 w-6 h-6 bg-red-600" />
</div> </div>

View File

@@ -1,24 +1,36 @@
<h3 class="text-lg">badges</h3> <h3 class="text-lg">badges</h3>
<span <span
class="text-sm font-medium bg-green-100 py-1 px-2 rounded text-green-500 align-middle">Paid</span> class="text-sm font-medium bg-green-100 py-1 px-2 rounded text-green-500 align-middle"
>Paid</span
>
<span <span
class="text-sm font-medium bg-red-100 py-1 px-2 rounded text-red-500 align-middle">Overdue</span> class="text-sm font-medium bg-red-100 py-1 px-2 rounded text-red-500 align-middle"
<span >Overdue</span
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-blue-600">Primary</span> >
<span <span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-blue-600"
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-600">Secondary</span> >Primary</span
<span >
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-green-600">Success</span> <span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-600"
<span >Secondary</span
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-red-600">Danger</span> >
<span <span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-green-600"
class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-yellow-400">Warning</span> >Success</span
<span >
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-indigo-300">Info</span> <span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-red-600"
<span >Danger</span
class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-gray-200">Light</span> >
<span <span class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-yellow-400"
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-900">Dark</span> >Warning</span
>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-indigo-300"
>Info</span
>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-gray-200"
>Light</span
>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-900"
>Dark</span
>
<h3 class="text-lg">closable badges</h3> <h3 class="text-lg">closable badges</h3>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-blue-600"> <span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-blue-600">
Primary Primary

View File

@@ -13,9 +13,10 @@
class="h-3 w-3 stroke-current" class="h-3 w-3 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><path xmlns="http://www.w3.org/2000/svg"
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> ><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> <polyline points="9 22 9 12 15 12 15 22" /></svg
>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="/">Home</a><svg <a class="mr-2" href="/">Home</a><svg
@@ -28,12 +29,10 @@
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"
x1="5" ><line x1="5" y1="12" x2="19" y2="12" />
y1="12" <polyline points="12 5 19 12 12 19" /></svg
x2="19" >
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="/">Second level</a><svg <a class="mr-2" href="/">Second level</a><svg
@@ -46,12 +45,10 @@
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"
x1="5" ><line x1="5" y1="12" x2="19" y2="12" />
y1="12" <polyline points="12 5 19 12 12 19" /></svg
x2="19" >
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="/">Third level</a> <a class="mr-2" href="/">Third level</a>

View File

@@ -29,8 +29,7 @@
<div class="mb-8"> <div class="mb-8">
<Table /> <Table />
</div> </div>
<div <div class="widget w-full p-4 mb-4 rounded-lg bg-white border border-grey-100">
class="widget w-full p-4 mb-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between mb-6"> <div class="flex flex-row items-center justify-between mb-6">
<div class="flex flex-col"> <div class="flex flex-col">
<div class="text-sm font-light text-grey-500">Regular</div> <div class="text-sm font-light text-grey-500">Regular</div>
@@ -39,32 +38,38 @@
</div> </div>
<div class="flex flex-col lg:flex-row lg:flex-wrap w-full lg:space-x-4"> <div class="flex flex-col lg:flex-row lg:flex-wrap w-full lg:space-x-4">
<div class="w-full lg:w-1/4"> <div class="w-full lg:w-1/4">
<div class="form-element "> <div class="form-element">
<div class="form-label">Label</div><input <div class="form-label">Label</div>
<input
name="name" name="name"
type="text" type="text"
class="form-input" class="form-input"
placeholder="Enter something..." /> placeholder="Enter something..."
/>
<div class="form-hint">This is a hint</div> <div class="form-hint">This is a hint</div>
</div> </div>
</div> </div>
<div class="w-full lg:w-1/4"> <div class="w-full lg:w-1/4">
<div class="form-element "> <div class="form-element">
<div class="form-label">First name</div><input <div class="form-label">First name</div>
<input
name="name" name="name"
type="text" type="text"
class="form-input form-input-invalid" class="form-input form-input-invalid"
placeholder="john@example.com" /> placeholder="john@example.com"
/>
<div class="form-error">First name is required</div> <div class="form-error">First name is required</div>
</div> </div>
</div> </div>
<div class="w-full lg:w-1/4"> <div class="w-full lg:w-1/4">
<div class="form-element "> <div class="form-element">
<div class="form-label">First name</div><input <div class="form-label">First name</div>
<input
name="name" name="name"
type="text" type="text"
class="form-input form-input-valid" class="form-input form-input-valid"
placeholder="john@example.com" /> placeholder="john@example.com"
/>
<div class="form-success">First name is valid</div> <div class="form-success">First name is valid</div>
</div> </div>
</div> </div>

View File

@@ -4,31 +4,55 @@
<div class="text-sm font-light text-grey-500">Conversions</div> <div class="text-sm font-light text-grey-500">Conversions</div>
<div class="text-sm font-bold"><span>This year</span></div> <div class="text-sm font-bold"><span>This year</span></div>
</div> </div>
<div class="relative"><button <div class="relative">
class="btn btn-default btn-circle btn-icon bg-transparent hover:bg-transparent active:bg-transparent relative"><svg <button
stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" class="btn btn-default btn-circle btn-icon bg-transparent hover:bg-transparent active:bg-transparent relative"
stroke-linejoin="round" class="stroke-current stroke-1" size="18" height="18" width="18" ><svg
xmlns="http://www.w3.org/2000/svg"> stroke="currentColor"
<circle cx="12" cy="12" r="1"></circle> fill="none"
<circle cx="12" cy="5" r="1"></circle> stroke-width="2"
<circle cx="12" cy="19" r="1"></circle> viewBox="0 0 24 24"
</svg></button> stroke-linecap="round"
<div class="dropdown absolute top-0 right-0 mt-8 "> stroke-linejoin="round"
class="stroke-current stroke-1"
size="18"
height="18"
width="18"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="12" cy="12" r="1" />
<circle cx="12" cy="5" r="1" />
<circle cx="12" cy="19" r="1" />
</svg></button
>
<div class="dropdown absolute top-0 right-0 mt-8">
<div class="dropdown-content w-48 bottom-start"> <div class="dropdown-content w-48 bottom-start">
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<ul class="list-none"> <ul class="list-none">
<li><a <li>
<a
class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100" class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100"
href="/">Today</a></li> href="/">Today</a
<li><a >
</li>
<li>
<a
class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100" class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100"
href="/">This week</a></li> href="/">This week</a
<li><a >
</li>
<li>
<a
class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100" class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100"
href="/">This month</a></li> href="/">This month</a
<li><a >
</li>
<li>
<a
class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100" class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100"
href="/">This year</a></li> href="/">This year</a
>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@@ -38,167 +62,418 @@
<div class="flex flex-row w-full"> <div class="flex flex-row w-full">
<div style="width:100%;height:240px"> <div style="width:100%;height:240px">
<div class="recharts-responsive-container" style="width:100%;height:100%"> <div class="recharts-responsive-container" style="width:100%;height:100%">
<div class="recharts-wrapper" <div
style="position: relative; cursor: default; width: 704px; height: 240px;"><svg class="recharts-wrapper"
class="recharts-surface" width="704" height="240" viewBox="0 0 704 240" version="1.1"> style="position: relative; cursor: default; width: 704px; height: 240px;"
>
<svg
class="recharts-surface"
width="704"
height="240"
viewBox="0 0 704 240"
version="1.1"
>
<defs> <defs>
<clipPath id="recharts3-clip"> <clipPath id="recharts3-clip">
<rect x="40" y="10" height="190" width="654"></rect> <rect x="40" y="10" height="190" width="654" />
</clipPath> </clipPath>
</defs> </defs>
<g class="recharts-layer recharts-cartesian-axis recharts-xAxis xAxis"> <g
class="recharts-layer recharts-cartesian-axis recharts-xAxis xAxis"
>
<g class="recharts-cartesian-axis-ticks"> <g class="recharts-cartesian-axis-ticks">
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" x="67.25" <g class="recharts-layer recharts-cartesian-axis-tick"
y="208" stroke="none" fill="#666" class="recharts-text recharts-cartesian-axis-tick-value" ><text
text-anchor="middle"> width="654"
height="30"
x="67.25"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="67.25" dy="0.71em">Jan</tspan> <tspan x="67.25" dy="0.71em">Jan</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="121.75" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="121.75"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="121.75" dy="0.71em">Feb</tspan> <tspan x="121.75" dy="0.71em">Feb</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="176.25" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="176.25"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="176.25" dy="0.71em">Mar</tspan> <tspan x="176.25" dy="0.71em">Mar</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="230.75" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="230.75"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="230.75" dy="0.71em">Apr</tspan> <tspan x="230.75" dy="0.71em">Apr</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="285.25" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="285.25"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="285.25" dy="0.71em">May</tspan> <tspan x="285.25" dy="0.71em">May</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="339.75" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="339.75"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="339.75" dy="0.71em">Jun</tspan> <tspan x="339.75" dy="0.71em">Jun</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="394.25" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="394.25"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="394.25" dy="0.71em">Jul</tspan> <tspan x="394.25" dy="0.71em">Jul</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="448.75" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="448.75"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="448.75" dy="0.71em">Aug</tspan> <tspan x="448.75" dy="0.71em">Aug</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="503.25" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="503.25"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="503.25" dy="0.71em">Sep</tspan> <tspan x="503.25" dy="0.71em">Sep</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="557.75" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="557.75"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="557.75" dy="0.71em">Oct</tspan> <tspan x="557.75" dy="0.71em">Oct</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="612.25" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="612.25"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="612.25" dy="0.71em">Nov</tspan> <tspan x="612.25" dy="0.71em">Nov</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="654" height="30" >
x="666.75" y="208" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="middle"> ><text
width="654"
height="30"
x="666.75"
y="208"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="middle"
>
<tspan x="666.75" dy="0.71em">Dec</tspan> <tspan x="666.75" dy="0.71em">Dec</tspan>
</text></g> </text></g
>
</g> </g>
</g> </g>
<g class="recharts-layer recharts-cartesian-axis recharts-yAxis yAxis"> <g
class="recharts-layer recharts-cartesian-axis recharts-yAxis yAxis"
>
<g class="recharts-cartesian-axis-ticks"> <g class="recharts-cartesian-axis-ticks">
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="30" height="190" x="32" <g class="recharts-layer recharts-cartesian-axis-tick"
y="200" stroke="none" fill="#666" class="recharts-text recharts-cartesian-axis-tick-value" ><text
text-anchor="end"> width="30"
height="190"
x="32"
y="200"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="end"
>
<tspan x="32" dy="0.355em">0</tspan> <tspan x="32" dy="0.355em">0</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="30" height="190" x="32" >
y="152.5" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="end"> ><text
width="30"
height="190"
x="32"
y="152.5"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="end"
>
<tspan x="32" dy="0.355em">65</tspan> <tspan x="32" dy="0.355em">65</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="30" height="190" x="32" >
y="105" stroke="none" fill="#666" class="recharts-text recharts-cartesian-axis-tick-value" <g class="recharts-layer recharts-cartesian-axis-tick"
text-anchor="end"> ><text
width="30"
height="190"
x="32"
y="105"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="end"
>
<tspan x="32" dy="0.355em">130</tspan> <tspan x="32" dy="0.355em">130</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="30" height="190" x="32" >
y="57.5" stroke="none" fill="#666" <g class="recharts-layer recharts-cartesian-axis-tick"
class="recharts-text recharts-cartesian-axis-tick-value" text-anchor="end"> ><text
width="30"
height="190"
x="32"
y="57.5"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="end"
>
<tspan x="32" dy="0.355em">195</tspan> <tspan x="32" dy="0.355em">195</tspan>
</text></g> </text></g
<g class="recharts-layer recharts-cartesian-axis-tick"><text width="30" height="190" x="32" >
y="10" stroke="none" fill="#666" class="recharts-text recharts-cartesian-axis-tick-value" <g class="recharts-layer recharts-cartesian-axis-tick"
text-anchor="end"> ><text
width="30"
height="190"
x="32"
y="10"
stroke="none"
fill="#666"
class="recharts-text recharts-cartesian-axis-tick-value"
text-anchor="end"
>
<tspan x="32" dy="0.355em">260</tspan> <tspan x="32" dy="0.355em">260</tspan>
</text></g> </text></g
>
</g> </g>
</g> </g>
<g class="recharts-layer recharts-bar"> <g class="recharts-layer recharts-bar">
<g class="recharts-layer recharts-bar-rectangles"> <g class="recharts-layer recharts-bar-rectangles">
<g class="recharts-layer"> <g class="recharts-layer">
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="119.11538461538461" x="55" y="80.88461538461539" <path
radius="0" class="recharts-rectangle" fill="#90caf9"
d="M 55,80.88461538461539 h 10 v 119.11538461538461 h -10 Z"></path> width="10"
height="119.11538461538461"
x="55"
y="80.88461538461539"
radius="0"
class="recharts-rectangle"
d="M 55,80.88461538461539 h 10 v 119.11538461538461 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="95" x="109.5" y="105" radius="0" <path
class="recharts-rectangle" d="M 109.5,105 h 10 v 95 h -10 Z"></path> fill="#90caf9"
width="10"
height="95"
x="109.5"
y="105"
radius="0"
class="recharts-rectangle"
d="M 109.5,105 h 10 v 95 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="122.03846153846155" x="164" y="77.96153846153845" <path
radius="0" class="recharts-rectangle" fill="#90caf9"
d="M 164,77.96153846153845 h 10 v 122.03846153846155 h -10 Z"></path> width="10"
height="122.03846153846155"
x="164"
y="77.96153846153845"
radius="0"
class="recharts-rectangle"
d="M 164,77.96153846153845 h 10 v 122.03846153846155 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="81.11538461538461" x="218.5" <path
y="118.88461538461539" radius="0" class="recharts-rectangle" fill="#90caf9"
d="M 218.5,118.88461538461539 h 10 v 81.11538461538461 h -10 Z"></path> width="10"
height="81.11538461538461"
x="218.5"
y="118.88461538461539"
radius="0"
class="recharts-rectangle"
d="M 218.5,118.88461538461539 h 10 v 81.11538461538461 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="114" x="273" y="86" radius="0" <path
class="recharts-rectangle" d="M 273,86 h 10 v 114 h -10 Z"></path> fill="#90caf9"
width="10"
height="114"
x="273"
y="86"
radius="0"
class="recharts-rectangle"
d="M 273,86 h 10 v 114 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="117.65384615384616" x="327.5" <path
y="82.34615384615384" radius="0" class="recharts-rectangle" fill="#90caf9"
d="M 327.5,82.34615384615384 h 10 v 117.65384615384616 h -10 Z"></path> width="10"
height="117.65384615384616"
x="327.5"
y="82.34615384615384"
radius="0"
class="recharts-rectangle"
d="M 327.5,82.34615384615384 h 10 v 117.65384615384616 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="103.76923076923076" x="382" y="96.23076923076924" <path
radius="0" class="recharts-rectangle" fill="#90caf9"
d="M 382,96.23076923076924 h 10 v 103.76923076923076 h -10 Z"></path> width="10"
height="103.76923076923076"
x="382"
y="96.23076923076924"
radius="0"
class="recharts-rectangle"
d="M 382,96.23076923076924 h 10 v 103.76923076923076 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="92.80769230769232" x="436.5" <path
y="107.19230769230768" radius="0" class="recharts-rectangle" fill="#90caf9"
d="M 436.5,107.19230769230768 h 10 v 92.80769230769232 h -10 Z"></path> width="10"
height="92.80769230769232"
x="436.5"
y="107.19230769230768"
radius="0"
class="recharts-rectangle"
d="M 436.5,107.19230769230768 h 10 v 92.80769230769232 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="92.80769230769232" x="491" y="107.19230769230768" <path
radius="0" class="recharts-rectangle" fill="#90caf9"
d="M 491,107.19230769230768 h 10 v 92.80769230769232 h -10 Z"></path> width="10"
height="92.80769230769232"
x="491"
y="107.19230769230768"
radius="0"
class="recharts-rectangle"
d="M 491,107.19230769230768 h 10 v 92.80769230769232 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="127.8846153846154" x="545.5" y="72.1153846153846" <path
radius="0" class="recharts-rectangle" fill="#90caf9"
d="M 545.5,72.1153846153846 h 10 v 127.8846153846154 h -10 Z"></path> width="10"
height="127.8846153846154"
x="545.5"
y="72.1153846153846"
radius="0"
class="recharts-rectangle"
d="M 545.5,72.1153846153846 h 10 v 127.8846153846154 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="105.23076923076924" x="600" y="94.76923076923076" <path
radius="0" class="recharts-rectangle" fill="#90caf9"
d="M 600,94.76923076923076 h 10 v 105.23076923076924 h -10 Z"></path> width="10"
height="105.23076923076924"
x="600"
y="94.76923076923076"
radius="0"
class="recharts-rectangle"
d="M 600,94.76923076923076 h 10 v 105.23076923076924 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#90caf9" width="10" height="115.46153846153845" x="654.5" <path
y="84.53846153846155" radius="0" class="recharts-rectangle" fill="#90caf9"
d="M 654.5,84.53846153846155 h 10 v 115.46153846153845 h -10 Z"></path> width="10"
height="115.46153846153845"
x="654.5"
y="84.53846153846155"
radius="0"
class="recharts-rectangle"
d="M 654.5,84.53846153846155 h 10 v 115.46153846153845 h -10 Z"
/>
</g> </g>
</g> </g>
</g> </g>
@@ -207,75 +482,162 @@
<g class="recharts-layer recharts-bar-rectangles"> <g class="recharts-layer recharts-bar-rectangles">
<g class="recharts-layer"> <g class="recharts-layer">
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="112.53846153846155" x="69" y="87.46153846153845" <path
radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 69,87.46153846153845 h 10 v 112.53846153846155 h -10 Z"></path> width="10"
height="112.53846153846155"
x="69"
y="87.46153846153845"
radius="0"
class="recharts-rectangle"
d="M 69,87.46153846153845 h 10 v 112.53846153846155 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="151.26923076923077" x="123.5" <path
y="48.730769230769226" radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 123.5,48.730769230769226 h 10 v 151.26923076923077 h -10 Z"></path> width="10"
height="151.26923076923077"
x="123.5"
y="48.730769230769226"
radius="0"
class="recharts-rectangle"
d="M 123.5,48.730769230769226 h 10 v 151.26923076923077 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="181.23076923076923" x="178" y="18.769230769230774" <path
radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 178,18.769230769230774 h 10 v 181.23076923076923 h -10 Z"></path> width="10"
height="181.23076923076923"
x="178"
y="18.769230769230774"
radius="0"
class="recharts-rectangle"
d="M 178,18.769230769230774 h 10 v 181.23076923076923 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="165.8846153846154" x="232.5" y="34.11538461538461" <path
radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 232.5,34.11538461538461 h 10 v 165.8846153846154 h -10 Z"></path> width="10"
height="165.8846153846154"
x="232.5"
y="34.11538461538461"
radius="0"
class="recharts-rectangle"
d="M 232.5,34.11538461538461 h 10 v 165.8846153846154 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="156.38461538461536" x="287" y="43.61538461538464" <path
radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 287,43.61538461538464 h 10 v 156.38461538461536 h -10 Z"></path> width="10"
height="156.38461538461536"
x="287"
y="43.61538461538464"
radius="0"
class="recharts-rectangle"
d="M 287,43.61538461538464 h 10 v 156.38461538461536 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="118.38461538461539" x="341.5" <path
y="81.61538461538461" radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 341.5,81.61538461538461 h 10 v 118.38461538461539 h -10 Z"></path> width="10"
height="118.38461538461539"
x="341.5"
y="81.61538461538461"
radius="0"
class="recharts-rectangle"
d="M 341.5,81.61538461538461 h 10 v 118.38461538461539 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="138.84615384615384" x="396" y="61.15384615384616" <path
radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 396,61.15384615384616 h 10 v 138.84615384615384 h -10 Z"></path> width="10"
height="138.84615384615384"
x="396"
y="61.15384615384616"
radius="0"
class="recharts-rectangle"
d="M 396,61.15384615384616 h 10 v 138.84615384615384 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="175.3846153846154" x="450.5" <path
y="24.615384615384613" radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 450.5,24.615384615384613 h 10 v 175.3846153846154 h -10 Z"></path> width="10"
height="175.3846153846154"
x="450.5"
y="24.615384615384613"
radius="0"
class="recharts-rectangle"
d="M 450.5,24.615384615384613 h 10 v 175.3846153846154 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="155.65384615384613" x="505" y="44.34615384615387" <path
radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 505,44.34615384615387 h 10 v 155.65384615384613 h -10 Z"></path> width="10"
height="155.65384615384613"
x="505"
y="44.34615384615387"
radius="0"
class="recharts-rectangle"
d="M 505,44.34615384615387 h 10 v 155.65384615384613 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="179.76923076923077" x="559.5" <path
y="20.230769230769226" radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 559.5,20.230769230769226 h 10 v 179.76923076923077 h -10 Z"></path> width="10"
height="179.76923076923077"
x="559.5"
y="20.230769230769226"
radius="0"
class="recharts-rectangle"
d="M 559.5,20.230769230769226 h 10 v 179.76923076923077 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="173.19230769230768" x="614" y="26.80769230769232" <path
radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 614,26.80769230769232 h 10 v 173.19230769230768 h -10 Z"></path> width="10"
height="173.19230769230768"
x="614"
y="26.80769230769232"
radius="0"
class="recharts-rectangle"
d="M 614,26.80769230769232 h 10 v 173.19230769230768 h -10 Z"
/>
</g> </g>
<g class="recharts-layer recharts-bar-rectangle"> <g class="recharts-layer recharts-bar-rectangle">
<path fill="#1e88e5" width="10" height="146.15384615384616" x="668.5" <path
y="53.84615384615384" radius="0" class="recharts-rectangle" fill="#1e88e5"
d="M 668.5,53.84615384615384 h 10 v 146.15384615384616 h -10 Z"></path> width="10"
height="146.15384615384616"
x="668.5"
y="53.84615384615384"
radius="0"
class="recharts-rectangle"
d="M 668.5,53.84615384615384 h 10 v 146.15384615384616 h -10 Z"
/>
</g> </g>
</g> </g>
</g> </g>
</g> </g>
</svg> </svg>
<div class="recharts-tooltip-wrapper" <div
style="pointer-events: none; visibility: hidden; position: absolute; top: 0px; transform: translate(538.875px, 126px);"> class="recharts-tooltip-wrapper"
</div> style="pointer-events: none; visibility: hidden; position: absolute; top: 0px; transform: translate(538.875px, 126px);"
/>
</div> </div>
<div style="position:absolute;width:0;height:0;visibility:hidden;display:none"></div> <div
style="position:absolute;width:0;height:0;visibility:hidden;display:none"
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,15 +1,18 @@
<!-- This example requires Tailwind CSS v2.0+ --> <!-- This example requires Tailwind CSS v2.0+ -->
<div <div
class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6"> class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6"
>
<div class="flex-1 flex justify-between sm:hidden"> <div class="flex-1 flex justify-between sm:hidden">
<a <a
href="#" href="#"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:text-gray-500"> class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:text-gray-500"
>
Previous Previous
</a> </a>
<a <a
href="#" href="#"
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:text-gray-500"> class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:text-gray-500"
>
Next Next
</a> </a>
</div> </div>
@@ -28,10 +31,12 @@
<div> <div>
<nav <nav
class="relative z-0 inline-flex shadow-sm -space-x-px" class="relative z-0 inline-flex shadow-sm -space-x-px"
aria-label="Pagination"> aria-label="Pagination"
>
<a <a
href="#" href="#"
class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"> class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
>
<span class="sr-only">Previous</span> <span class="sr-only">Previous</span>
<!-- Heroicon name: chevron-left --> <!-- Heroicon name: chevron-left -->
<svg <svg
@@ -39,50 +44,60 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" viewBox="0 0 20 20"
fill="currentColor" fill="currentColor"
aria-hidden="true"> aria-hidden="true"
>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clip-rule="evenodd" /> clip-rule="evenodd"
/>
</svg> </svg>
</a> </a>
<a <a
href="#" href="#"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"> class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
>
1 1
</a> </a>
<a <a
href="#" href="#"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"> class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
>
2 2
</a> </a>
<a <a
href="#" href="#"
class="hidden md:inline-flex relative items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"> class="hidden md:inline-flex relative items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
>
3 3
</a> </a>
<span <span
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700"> class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700"
>
... ...
</span> </span>
<a <a
href="#" href="#"
class="hidden md:inline-flex relative items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"> class="hidden md:inline-flex relative items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
>
8 8
</a> </a>
<a <a
href="#" href="#"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"> class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
>
9 9
</a> </a>
<a <a
href="#" href="#"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"> class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
>
10 10
</a> </a>
<a <a
href="#" href="#"
class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"> class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
>
<span class="sr-only">Next</span> <span class="sr-only">Next</span>
<!-- Heroicon name: chevron-right --> <!-- Heroicon name: chevron-right -->
<svg <svg
@@ -90,11 +105,13 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" viewBox="0 0 20 20"
fill="currentColor" fill="currentColor"
aria-hidden="true"> aria-hidden="true"
>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd" /> clip-rule="evenodd"
/>
</svg> </svg>
</a> </a>
</nav> </nav>

View File

@@ -7,14 +7,14 @@
</div> </div>
</div> </div>
</div> </div>
<div <div class="w-full p-4 mb-4 rounded-lg bg-white border border-grey-100">
class="w-full p-4 mb-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-start p-4"> <div class="flex flex-row items-center justify-start p-4">
<div class="flex-shrink-0 w-24"> <div class="flex-shrink-0 w-24">
<img <img
src="/images/faces/m1.png" src="/images/faces/m1.png"
alt="media" alt="media"
class="shadow rounded-full h-20 w-20 shadow-outline mb-2" /> class="shadow rounded-full h-20 w-20 shadow-outline mb-2"
/>
</div> </div>
<div class="py-2 px-2"> <div class="py-2 px-2">
<p class="text-base font-bold whitespace-no-wrap">Lucas Smith</p> <p class="text-base font-bold whitespace-no-wrap">Lucas Smith</p>
@@ -22,7 +22,8 @@
Vital Database Dude Vital Database Dude
</p> </p>
<div <div
class="flex flex-row items-center justify-start w-full py-1 space-x-2"> class="flex flex-row items-center justify-start w-full py-1 space-x-2"
>
<svg <svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
@@ -33,8 +34,11 @@
class="stroke-current text-xl text-twitter" class="stroke-current text-xl text-twitter"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><path xmlns="http://www.w3.org/2000/svg"
d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z" /></svg><svg ><path
d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"
/></svg
><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
@@ -44,8 +48,11 @@
class="stroke-current text-xl text-facebook" class="stroke-current text-xl text-facebook"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><path xmlns="http://www.w3.org/2000/svg"
d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" /></svg><svg ><path
d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"
/></svg
><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
@@ -55,21 +62,21 @@
class="stroke-current text-xl text-instagram" class="stroke-current text-xl text-instagram"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><rect xmlns="http://www.w3.org/2000/svg"
x="2" ><rect x="2" y="2" width="20" height="20" rx="5" ry="5" />
y="2"
width="20"
height="20"
rx="5"
ry="5" />
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" /> <path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" />
<line x1="17.5" y1="6.5" x2="17.5" y2="6.5" /></svg> <line x1="17.5" y1="6.5" x2="17.5" y2="6.5" /></svg
>
</div> </div>
</div> </div>
<div class="ml-auto flex-shrink-0 space-x-2 hidden lg:flex"> <div class="ml-auto flex-shrink-0 space-x-2 hidden lg:flex">
<button <button
class="btn btn-default btn-rounded bg-blue-500 hover:bg-blue-600 text-white">Subscribe</button><button class="btn btn-default btn-rounded bg-blue-500 hover:bg-blue-600 text-white"
class="btn btn-default btn-rounded bg-blue-500 hover:bg-blue-600 text-white">Follow</button> >Subscribe</button
><button
class="btn btn-default btn-rounded bg-blue-500 hover:bg-blue-600 text-white"
>Follow</button
>
</div> </div>
</div> </div>
<div class="flex flex-wrap"> <div class="flex flex-wrap">
@@ -77,14 +84,19 @@
<div class="flex flex-wrap flex-col w-full tabs"> <div class="flex flex-wrap flex-col w-full tabs">
<div class="flex lg:flex-wrap flex-row lg:space-x-2"> <div class="flex lg:flex-wrap flex-row lg:space-x-2">
<div class="flex-none"> <div class="flex-none">
<button class="tab tab-underline tab-active" type="button">Account <button class="tab tab-underline tab-active" type="button"
settings</button> >Account settings</button
>
</div> </div>
<div class="flex-none"> <div class="flex-none">
<button class="tab tab-underline" type="button">Email preferences</button> <button class="tab tab-underline" type="button"
>Email preferences</button
>
</div> </div>
<div class="flex-none"> <div class="flex-none">
<button class="tab tab-underline" type="button">Security settings</button> <button class="tab tab-underline" type="button"
>Security settings</button
>
</div> </div>
</div> </div>
<div class="tab-content block"> <div class="tab-content block">
@@ -93,51 +105,64 @@
<form class="form flex flex-wrap w-full"> <form class="form flex flex-wrap w-full">
<div class="w-full"> <div class="w-full">
<div class="form-element"> <div class="form-element">
<div class="form-label">First name</div><input <div class="form-label">First name</div>
<input
name="first-name" name="first-name"
type="text" type="text"
class="form-input " class="form-input"
placeholder="Enter you first name" /> placeholder="Enter you first name"
/>
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="form-label">Last name</div><input <div class="form-label">Last name</div>
<input
name="last-name" name="last-name"
type="text" type="text"
class="form-input " class="form-input"
placeholder="Enter you last name" /> placeholder="Enter you last name"
/>
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="form-label">Email address</div><input <div class="form-label">Email address</div>
<input
name="email" name="email"
type="email" type="email"
class="form-input " class="form-input"
placeholder="Enter you email address" /> placeholder="Enter you email address"
/>
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="form-label">Company</div><input <div class="form-label">Company</div>
<input
name="company" name="company"
type="text" type="text"
class="form-input " class="form-input"
placeholder="Enter you company" /> placeholder="Enter you company"
/>
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="form-label">Position</div><input <div class="form-label">Position</div>
<input
name="position" name="position"
type="text" type="text"
class="form-input " class="form-input"
placeholder="Enter you position" /> placeholder="Enter you position"
/>
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="form-label">Language</div><select <div class="form-label">Language</div>
name="language" <select name="language" class="form-select"
class="form-select "><option>Select language</option> ><option>Select language</option>
<option value="english">English</option> <option value="english">English</option>
<option value="spanish">Spanish</option> <option value="spanish">Spanish</option>
<option value="portuguese">Portuguese</option></select> <option value="portuguese">Portuguese</option></select
>
</div> </div>
</div><input </div>
<input
type="submit" type="submit"
class="btn btn-default bg-blue-500 hover:bg-blue-600 text-white btn-rounded" /> class="btn btn-default bg-blue-500 hover:bg-blue-600 text-white btn-rounded"
/>
</form> </form>
</div> </div>
</div> </div>
@@ -148,56 +173,70 @@
<form class="form flex flex-wrap w-full"> <form class="form flex flex-wrap w-full">
<div class="w-full"> <div class="w-full">
<div class="form-element"> <div class="form-element">
<div class="form-label">Current email</div><input <div class="form-label">Current email</div>
<input
name="email" name="email"
type="email" type="email"
class="form-input " class="form-input"
placeholder="Enter you current email address" /> placeholder="Enter you current email address"
/>
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="form-label">New email</div><input <div class="form-label">New email</div>
<input
name="email" name="email"
type="email" type="email"
class="form-input " class="form-input"
placeholder="Enter you new email address" /> placeholder="Enter you new email address"
/>
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="form-label">Daily updates</div> <div class="form-label">Daily updates</div>
<div class="flex items-center justify-start space-x-2"> <div class="flex items-center justify-start space-x-2">
<label <label class="flex items-center justify-start space-x-2"
class="flex items-center justify-start space-x-2"><input ><input
type="radio" type="radio"
name="daily-updates" name="daily-updates"
class="form-radio h-4 w-4 " class="form-radio h-4 w-4"
value="yes" /><span value="yes"
class="">Yes</span></label><label /><span class="">Yes</span></label
class="flex items-center justify-start space-x-2"><input ><label
class="flex items-center justify-start space-x-2"
><input
type="radio" type="radio"
name="daily-updates" name="daily-updates"
class="form-radio h-4 w-4 " class="form-radio h-4 w-4"
value="no" /><span class="">No</span></label> value="no"
/><span class="">No</span></label
>
</div> </div>
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="form-label">Weekly updates</div> <div class="form-label">Weekly updates</div>
<div class="flex items-center justify-start space-x-2"> <div class="flex items-center justify-start space-x-2">
<label <label class="flex items-center justify-start space-x-2"
class="flex items-center justify-start space-x-2"><input ><input
type="radio" type="radio"
name="weekle-updates" name="weekle-updates"
class="form-radio h-4 w-4 " class="form-radio h-4 w-4"
value="yes" /><span value="yes"
class="">Yes</span></label><label /><span class="">Yes</span></label
class="flex items-center justify-start space-x-2"><input ><label
class="flex items-center justify-start space-x-2"
><input
type="radio" type="radio"
name="weekle-updates" name="weekle-updates"
class="form-radio h-4 w-4 " class="form-radio h-4 w-4"
value="no" /><span class="">No</span></label> value="no"
/><span class="">No</span></label
>
</div> </div>
</div> </div>
</div><input </div>
<input
type="submit" type="submit"
class="btn btn-default bg-blue-500 hover:bg-blue-600 text-white btn-rounded" /> class="btn btn-default bg-blue-500 hover:bg-blue-600 text-white btn-rounded"
/>
</form> </form>
</div> </div>
</div> </div>
@@ -208,29 +247,37 @@
<form class="form flex flex-wrap w-full"> <form class="form flex flex-wrap w-full">
<div class="w-full"> <div class="w-full">
<div class="form-element"> <div class="form-element">
<div class="form-label">Current password</div><input <div class="form-label">Current password</div>
<input
name="current-password" name="current-password"
type="password" type="password"
class="form-input " class="form-input"
placeholder="Enter your current password" /> placeholder="Enter your current password"
/>
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="form-label">New password</div><input <div class="form-label">New password</div>
<input
name="new-password" name="new-password"
type="password" type="password"
class="form-input " class="form-input"
placeholder="Enter your new password" /> placeholder="Enter your new password"
/>
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="form-label">Confirm new password</div><input <div class="form-label">Confirm new password</div>
<input
name="confirm-new-password" name="confirm-new-password"
type="password" type="password"
class="form-input " class="form-input"
placeholder="Enter your new password confirmation" /> placeholder="Enter your new password confirmation"
/>
</div> </div>
</div><input </div>
<input
type="submit" type="submit"
class="btn btn-default bg-blue-500 hover:bg-blue-600 text-white btn-rounded" /> class="btn btn-default bg-blue-500 hover:bg-blue-600 text-white btn-rounded"
/>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -3,44 +3,50 @@
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> <div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"> <div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
<div <div
class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"> class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"
>
<table class="min-w-full divide-y divide-gray-200"> <table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50"> <thead class="bg-gray-50">
<tr> <tr class="odd:bg-white even:bg-gray-100">
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Name Name
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Title Title
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Status Status
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Role Role
</th> </th>
<th scope="col" class="relative px-6 py-3"> <th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('edit')}</span> <span class="sr-only">{$_("edit")}</span>
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-gray-200"> <tbody class="divide-y divide-gray-200">
<tr> <tr class="odd:bg-white even:bg-gray-100">
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center"> <div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10"> <div class="flex-shrink-0 h-10 w-10">
<img <img
class="h-10 w-10 rounded-full" class="h-10 w-10 rounded-full"
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=60" src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=60"
alt="" /> alt=""
/>
</div> </div>
<div class="ml-4"> <div class="ml-4">
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
@@ -60,7 +66,8 @@
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<span <span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>
Active Active
</span> </span>
</td> </td>
@@ -68,10 +75,11 @@
Admin Admin
</td> </td>
<td <td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
<a >
href="#" <a href="#" class="text-indigo-600 hover:text-indigo-900"
class="text-indigo-600 hover:text-indigo-900">{$_('edit')}</a> >{$_("edit")}</a
>
</td> </td>
</tr> </tr>

View File

@@ -1,18 +1,23 @@
<h3 class="text-lg">Tabs</h3> <h3 class="text-lg">Tabs</h3>
<div <div
class="w-full flex sm:border-b sm:border-gray-300 relative flex-col sm:flex-row"> class="w-full flex sm:border-b sm:border-gray-300 relative flex-col sm:flex-row"
>
<div <div
class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false"> class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false"
>
1 1
</div> </div>
<div <div
class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false"> class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false"
>
2 2
</div> </div>
<div <div
class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false"> class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false"
>
3 3
</div> </div>
<div <div
class="hidden sm:block absolute bottom-0 left-0 h-1 bg-blue-400 transition-transform duration-300 ease-out w-1/4 transform translate-x-double" /> class="hidden sm:block absolute bottom-0 left-0 h-1 bg-blue-400 transition-transform duration-300 ease-out w-1/4 transform translate-x-double"
/>
</div> </div>

View File

@@ -1,6 +1,7 @@
<div> <div>
<div <div
class="text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-blue-200 text-blue-700 rounded-full"> class="text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-blue-200 text-blue-700 rounded-full"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"
@@ -11,7 +12,8 @@
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="feather feather-bell-off mr-2"> class="feather feather-bell-off mr-2"
>
<path d="M13.73 21a2 2 0 0 1-3.46 0" /> <path d="M13.73 21a2 2 0 0 1-3.46 0" />
<path d="M18.63 13A17.89 17.89 0 0 1 18 8" /> <path d="M18.63 13A17.89 17.89 0 0 1 18 8" />
<path d="M6.26 6.26A5.86 5.86 0 0 0 6 8c0 7-3 9-3 9h14" /> <path d="M6.26 6.26A5.86 5.86 0 0 0 6 8c0 7-3 9-3 9h14" />
@@ -22,7 +24,8 @@
</div> </div>
<div <div
class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-green-200 text-green-700 rounded-full"> class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-green-200 text-green-700 rounded-full"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"
@@ -33,7 +36,8 @@
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="feather feather-arrow-right mr-2"> class="feather feather-arrow-right mr-2"
>
<line x1="5" y1="12" x2="19" y2="12" /> <line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /> <polyline points="12 5 19 12 12 19" />
</svg> </svg>
@@ -41,7 +45,8 @@
</div> </div>
<div <div
class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-orange-200 text-orange-700 rounded-full"> class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-orange-200 text-orange-700 rounded-full"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"
@@ -52,14 +57,16 @@
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="feather feather-activity mr-2"> class="feather feather-activity mr-2"
>
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" /> <polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
</svg> </svg>
Tag Tag
</div> </div>
<div <div
class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-red-200 text-red-700 rounded-full"> class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-red-200 text-red-700 rounded-full"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"
@@ -70,7 +77,8 @@
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="feather feather-archive mr-2"> class="feather feather-archive mr-2"
>
<polyline points="21 8 21 21 3 21 3 8" /> <polyline points="21 8 21 21 3 21 3 8" />
<rect x="1" y="3" width="22" height="5" /> <rect x="1" y="3" width="22" height="5" />
<line x1="10" y1="12" x2="14" y2="12" /> <line x1="10" y1="12" x2="14" y2="12" />
@@ -79,7 +87,8 @@
</div> </div>
<div <div
class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 rounded-full bg-white text-gray-700 border"> class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 rounded-full bg-white text-gray-700 border"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"
@@ -90,10 +99,12 @@
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="feather feather-hard-drive mr-2"> class="feather feather-hard-drive mr-2"
>
<line x1="22" y1="12" x2="2" y2="12" /> <line x1="22" y1="12" x2="2" y2="12" />
<path <path
d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"
/>
<line x1="6" y1="16" x2="6.01" y2="16" /> <line x1="6" y1="16" x2="6.01" y2="16" />
<line x1="10" y1="16" x2="10.01" y2="16" /> <line x1="10" y1="16" x2="10.01" y2="16" />
</svg> </svg>

View File

@@ -1,7 +1,7 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { import {
RunnerService, RunnerService,
RunnerTeamService, RunnerTeamService,
@@ -9,10 +9,10 @@
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
import isMobilePhone from "validator/es/lib/isMobilePhone"; import isMobilePhone from "validator/es/lib/isMobilePhone";
import Toastify from "toastify-js";
import Select from "svelte-select"; import Select from "svelte-select";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import toast from 'svelte-french-toast'
export let modal_open; export let modal_open;
$: selected_team = undefined; $: selected_team = undefined;
@@ -78,10 +78,6 @@
function submit() { function submit() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({
text: $_("runner-is-being-added"),
duration: -1,
}).showToast();
let postdata = { let postdata = {
group: selected_team, group: selected_team,
firstname: firstname_input_value, firstname: firstname_input_value,
@@ -96,6 +92,7 @@
if (email_input_value) { if (email_input_value) {
postdata.email = email_input_value; postdata.email = email_input_value;
} }
toast.loading($_("runner-is-being-added"));
RunnerService.runnerControllerPost(postdata) RunnerService.runnerControllerPost(postdata)
.then((result) => { .then((result) => {
firstname_input_value = ""; firstname_input_value = "";
@@ -104,11 +101,8 @@
email_input_value = ""; email_input_value = "";
modal_open = false; modal_open = false;
// //
Toastify({ toast.dismiss();
text: $_("runner-added"), toast.success($_("runner-added"));
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
dispatch("created", { runners: [result] }); dispatch("created", { runners: [result] });
}) })
.catch((err) => { .catch((err) => {
@@ -116,8 +110,6 @@
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
//
toast.hideToast();
}); });
} }
} }
@@ -126,58 +118,70 @@
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;
}}> }}
>
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" /> data-id="modal_backdrop"
/>
</div> </div>
<span <span
class="hidden sm:inline-block sm:align-middle sm:h-screen" class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span> aria-hidden="true">&#8203;</span
>
<div <div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline"> aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600" class="h-6 w-6 text-blue-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <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> 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>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-runner')} {$_("create-a-new-runner")}
</h3> </h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_('please-provide-the-required-information-to-add-a-new-runner')} {$_(
"please-provide-the-required-information-to-add-a-new-runner"
)}
</p> </p>
</div> </div>
<div class="grid grid-cols-6 gap-6"> <div class="grid grid-cols-6 gap-6">
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="firstname" for="firstname"
class="block text-sm font-medium text-gray-700">{$_('first-name')}</label> class="block text-sm font-medium text-gray-700"
>{$_("first-name")}</label
>
<input <input
use:focus use:focus
autocomplete="off" autocomplete="off"
placeholder={$_('first-name')} placeholder={$_("first-name")}
class:border-red-500={!isFirstnameValid} class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid} class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid} class:focus:ring-red-500={!isFirstnameValid}
@@ -185,34 +189,41 @@
bind:this={firstname_input} bind:this={firstname_input}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isFirstnameValid} {#if !isFirstnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('first-name-is-required')} >
{$_("first-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="trackname" 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 <input
autocomplete="off" autocomplete="off"
placeholder={$_('middle-name')} placeholder={$_("middle-name")}
bind:value={middlename_input_value} bind:value={middlename_input_value}
bind:this={middlename_input} bind:this={middlename_input}
type="text" type="text"
name="trackname" 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>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="lastname" for="lastname"
class="block text-sm font-medium text-gray-700">{$_('last-name')}</label> class="block text-sm font-medium text-gray-700"
>{$_("last-name")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('last-name')} placeholder={$_("last-name")}
class:border-red-500={!isLastnameValid} class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid} class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid} class:focus:ring-red-500={!isLastnameValid}
@@ -220,41 +231,49 @@
bind:this={lastname_input} bind:this={lastname_input}
type="text" type="text"
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 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} {#if !isLastnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('last-name-is-required')} >
{$_("last-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="team" for="team"
class="block text-sm font-medium text-gray-700">{$_('team')}</label> class="block text-sm font-medium text-gray-700"
>{$_("team")}</label
>
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => label itemFilter={(label, filterText, option) =>
.toLowerCase() label.toLowerCase().includes(filterText.toLowerCase()) ||
.includes( option.value.id
filterText.toLowerCase()
) || option.value.id
.toString() .toString()
.startsWith(filterText.toLowerCase())} .startsWith(filterText.toLowerCase())}
items={orgs.concat(teams)} items={orgs.concat(teams)}
showChevron={true} showChevron={true}
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')} placeholder={$_(
noOptionsMessage={$_('no-organization-or-team-found')} "search-for-an-organization-or-team-by-name-or-id"
on:select={(selectedValue) => (selected_team = selectedValue.detail.value.id)} )}
on:clear={() => (selected_team = null)} /> noOptionsMessage={$_("no-organization-or-team-found")}
on:select={(selectedValue) =>
(selected_team = selectedValue.detail.value.id)}
on:clear={() => (selected_team = null)}
/>
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="phone" for="phone"
class="block text-sm font-medium text-gray-700">{$_('phone')}</label> class="block text-sm font-medium text-gray-700"
>{$_("phone")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('phone')} placeholder={$_("phone")}
class:border-red-500={!isPhoneValidOrEmpty} class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty} class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty} class:focus:ring-red-500={!isPhoneValidOrEmpty}
@@ -262,21 +281,27 @@
bind:this={phone_input} bind:this={phone_input}
type="tel" type="tel"
name="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" /> 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} {#if !isPhoneValidOrEmpty}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')} >
{@html $_(
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number"
)}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="email" 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 <input
autocomplete="off" autocomplete="off"
placeholder={$_('e-mail-adress')} placeholder={$_("e-mail-adress")}
class:border-red-500={!isEmailValidOrEmpty} class:border-red-500={!isEmailValidOrEmpty}
class:focus:border-red-500={!isEmailValidOrEmpty} class:focus:border-red-500={!isEmailValidOrEmpty}
class:focus:ring-red-500={!isEmailValidOrEmpty} class:focus:ring-red-500={!isEmailValidOrEmpty}
@@ -284,11 +309,13 @@
bind:this={email_input} bind:this={email_input}
type="email" type="email"
name="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} {#if !isEmailValidOrEmpty}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-email-is-required')} >
{$_("valid-email-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
@@ -302,16 +329,18 @@
class:opacity-50={!createbtnenabled} class:opacity-50={!createbtnenabled}
on:click={submit} on:click={submit}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('create')} >
{$_("create")}
</button> </button>
<button <button
on:click={() => { on:click={() => {
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
{$_('cancel')} >
{$_("cancel")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -25,7 +25,7 @@
}); });
async function submit() { async function submit() {
dispatch("delete", { id: delete_runner.id }); dispatch("delete", { id: delete_runner.id });
modal_open=false; modal_open = false;
} }
</script> </script>

View File

@@ -3,8 +3,7 @@
import { read as readXlsx, utils as xlsx_utils } from "xlsx"; import { read as readXlsx, utils as xlsx_utils } from "xlsx";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import Toastify from "toastify-js";
import { import {
ImportService, ImportService,
RunnerTeamService, RunnerTeamService,
@@ -12,6 +11,7 @@
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import Select from "svelte-select"; import Select from "svelte-select";
import toast from "svelte-french-toast";
export let opened_from; export let opened_from;
export let passed_org; export let passed_org;
export let passed_orgs; export let passed_orgs;
@@ -90,10 +90,7 @@
} }
function importAction() { function importAction() {
if (recent_processed === true) { if (recent_processed === true) {
const toast = Toastify({ toast.loading($_("runners-are-being-imported"));
text: $_("runners-are-being-imported"),
duration: -1,
}).showToast();
recent_processed = false; recent_processed = false;
const mapped = json_output.map(function (runner) { const mapped = json_output.map(function (runner) {
return { return {
@@ -115,48 +112,30 @@
if (opened_from === "OrgOverview" || opened_from === "OrgDetail") { if (opened_from === "OrgOverview" || opened_from === "OrgDetail") {
ImportService.importControllerPostOrgsJson(org, mapped) ImportService.importControllerPostOrgsJson(org, mapped)
.then((resp) => { .then((resp) => {
toast.hideToast(); toast.dismiss();
recent_processed = true; recent_processed = true;
Toastify({ toast.success($_("import-finished"));
text: $_("import-finished"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
cancelModal(); cancelModal();
}) })
.catch((err) => { .catch((err) => {
toast.hideToast(); toast.dismiss();
recent_processed = true; recent_processed = true;
Toastify({ toast.error($_("error-during-import"));
text: $_("error-during-import"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
cancelModal(); cancelModal();
}); });
} }
if (opened_from === "TeamDetail") { if (opened_from === "TeamDetail") {
ImportService.importControllerPostTeamsJson(passed_team.id, mapped) ImportService.importControllerPostTeamsJson(passed_team.id, mapped)
.then((resp) => { .then((resp) => {
toast.hideToast(); toast.dismiss();
recent_processed = true; recent_processed = true;
Toastify({ toast.success($_("import-finished"));
text: $_("import-finished"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
cancelModal(); cancelModal();
}) })
.catch((err) => { .catch((err) => {
toast.hideToast(); toast.dismiss();
recent_processed = true; recent_processed = true;
Toastify({ toast.error($_("error-during-import"));
text: $_("error-during-import"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
cancelModal(); cancelModal();
}); });
} }
@@ -169,24 +148,15 @@
) )
.then((resp) => { .then((resp) => {
dispatch("created", { runners: resp }); dispatch("created", { runners: resp });
toast.hideToast(); toast.dismiss();
recent_processed = true; recent_processed = true;
Toastify({ toast($_("import-finished"));
text: $_("import-finished"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
cancelModal(); cancelModal();
}) })
.catch((err) => { .catch((err) => {
toast.hideToast(); toast.dismiss();
recent_processed = true; recent_processed = true;
Toastify({ toast.error($_("error-during-import"));
text: $_("error-during-import"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
cancelModal(); cancelModal();
}); });
} }
@@ -198,24 +168,15 @@
) )
.then((resp) => { .then((resp) => {
dispatch("created", { runners: resp }); dispatch("created", { runners: resp });
toast.hideToast(); toast.dismiss();
recent_processed = true; recent_processed = true;
Toastify({ toast($_("import-finished"));
text: $_('import-finished'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
cancelModal(); cancelModal();
}) })
.catch((err) => { .catch((err) => {
toast.hideToast(); toast.dismiss();
recent_processed = true; recent_processed = true;
Toastify({ toast.error($_("error-during-import"));
text: $_("error-during-import"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
cancelModal(); cancelModal();
}); });
} }
@@ -227,43 +188,51 @@
{#if import_modal_open} {#if import_modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
cancelModal(); cancelModal();
}}> }}
>
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" /> data-id="modal_backdrop"
/>
</div> </div>
<span <span
class="hidden sm:inline-block sm:align-middle sm:h-screen" class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span> aria-hidden="true">&#8203;</span
>
<div <div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-max sm:w-full" class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-max sm:w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline"> aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600" class="h-6 w-6 text-blue-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <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> 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>
<div class="mt-3 text-center sm:mt-0 sm:ml-2 sm:text-left w-full"> <div class="mt-3 text-center sm:mt-0 sm:ml-2 sm:text-left w-full">
<h3 class="text-lg leading-6 font-bold mt-2 text-gray-900"> <h3 class="text-lg leading-6 font-bold mt-2 text-gray-900">
{$_('runner-import')} {$_("runner-import")}
</h3> </h3>
</div> </div>
</div> </div>
@@ -271,14 +240,15 @@
{#if json_output.length === 0} {#if json_output.length === 0}
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_('please-provide-the-required-csv-xlsx-file')} {$_("please-provide-the-required-csv-xlsx-file")}
</p> </p>
</div> </div>
<div class="overflow-hidden relative mt-4 mb-4"> <div class="overflow-hidden relative mt-4 mb-4">
<input <input
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
bind:files bind:files
type="file" /> type="file"
/>
</div> </div>
<div class="overflow-hidden relative mt-4 mb-4"> <div class="overflow-hidden relative mt-4 mb-4">
<button <button
@@ -286,47 +256,50 @@
cancelModal(); cancelModal();
}} }}
type="button" 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"> 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')} >
{$_("cancel")}
</button> </button>
</div> </div>
{/if} {/if}
{#if json_output.length > 0} {#if json_output.length > 0}
{#if opened_from === 'OrgOverview'} {#if opened_from === "OrgOverview"}
<p>{$_('import__target-organization')}</p> <p>{$_("import__target-organization")}</p>
<select <select
name="team" name="team"
bind:value={selected_org} bind:value={selected_org}
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"
>
{#each passed_orgs as o} {#each passed_orgs as o}
<option value={o.id}>{o.name}</option> <option value={o.id}>{o.name}</option>
{/each} {/each}
</select> </select>
<p>{$_('bitte-bestaetige-diese-laeufer-fuer-den-import')}</p> <p>{$_("confirm-runner-import")}</p>
{/if} {/if}
{#if opened_from === 'RunnerOverview'} {#if opened_from === "RunnerOverview"}
<p>{$_('group')}</p> <p>{$_("group")}</p>
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => label itemFilter={(label, filterText, option) =>
.toLowerCase() label.toLowerCase().includes(filterText.toLowerCase()) ||
.includes( option.id.value
filterText.toLowerCase()
) || option.id.value
.toString() .toString()
.startsWith(filterText.toLowerCase())} .startsWith(filterText.toLowerCase())}
items={groups} items={groups}
showChevron={true} showChevron={true}
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')} placeholder={$_(
noOptionsMessage={$_('no-organization-or-team-found')} "search-for-an-organization-or-team-by-name-or-id"
)}
noOptionsMessage={$_("no-organization-or-team-found")}
on:select={(selectedValue) => { on:select={(selectedValue) => {
selected_org_or_team = selectedValue.detail.value; selected_org_or_team = selectedValue.detail.value;
}} }}
on:clear={() => (selected_org_or_team = null)} /> on:clear={() => (selected_org_or_team = null)}
/>
{/if} {/if}
{#if opened_from === 'OrgDetail'} {#if opened_from === "OrgDetail"}
<p> <p>
{$_('runnerimport_verify_runners_org', { {$_("runnerimport_verify_runners_org", {
values: { org_name: passed_org.name }, values: { org_name: passed_org.name },
})} })}
</p> </p>
@@ -334,34 +307,39 @@
<input <input
type="search" type="search"
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_('datatable.search')} placeholder={$_("datatable.search")}
aria-label={$_('datatable.search')} aria-label={$_("datatable.search")}
class="p-2 w-full" /> class="p-2 w-full"
/>
<div class="relative w-full mt-4 mb-4"> <div class="relative w-full mt-4 mb-4">
<div class="w-full overflow-x-auto"> <div class="w-full overflow-x-auto">
<table class="divide-y divide-gray-200 w-full"> <table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50"> <thead class="bg-gray-50">
<tr> <tr class="odd:bg-white even:bg-gray-100">
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('csv_import__firstname')} >
{$_("csv_import__firstname")}
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('csv_import__middlename')} >
{$_("csv_import__middlename")}
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('csv_import__lastname')} >
{$_("csv_import__lastname")}
</th> </th>
{#if (opened_from !== 'TeamDetail' && opened_from !== 'RunnerOverview') || (opened_from === 'RunnerOverview' && selected_org_or_team.includes('ORG_'))} {#if (opened_from !== "TeamDetail" && opened_from !== "RunnerOverview") || (opened_from === "RunnerOverview" && selected_org_or_team.includes("ORG_"))}
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('csv_import__team')} >
{$_("csv_import__team")}
</th> </th>
{/if} {/if}
</tr> </tr>
@@ -372,19 +350,21 @@
.toString() .toString()
.toLowerCase() .toLowerCase()
.includes(searchvalue)} .includes(searchvalue)}
<tr> <tr class="odd:bg-white even:bg-gray-100">
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_('csv_import__firstname')}`]} {runner[`${$_("csv_import__firstname")}`]}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_('csv_import__middlename')}`] || ''} {runner[`${$_("csv_import__middlename")}`] || ""}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_('csv_import__lastname')}`]} {runner[`${$_("csv_import__lastname")}`]}
</td> </td>
{#if (opened_from !== 'TeamDetail' && opened_from !== 'RunnerOverview') || (opened_from === 'RunnerOverview' && selected_org_or_team.includes('ORG_'))} {#if (opened_from !== "TeamDetail" && opened_from !== "RunnerOverview") || (opened_from === "RunnerOverview" && selected_org_or_team.includes("ORG_"))}
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_('csv_import__team')}`] || runner[`${$_('csv_import__class')}`] || '---'} {runner[`${$_("csv_import__team")}`] ||
runner[`${$_("csv_import__class")}`] ||
"---"}
</td> </td>
{/if} {/if}
</tr> </tr>
@@ -398,16 +378,18 @@
class:opacity-50={!importButtonEnabled} class:opacity-50={!importButtonEnabled}
on:click={importAction} on:click={importAction}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('import-runners')} >
{$_("import-runners")}
</button> </button>
<button <button
on:click={() => { on:click={() => {
cancelModal(); cancelModal();
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('cancel')} >
{$_("cancel")}
</button> </button>
</div> </div>
{/if} {/if}

View File

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

View File

@@ -11,29 +11,29 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("runners")} {$_("runners")}
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")} </h4>
<button {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
on:click={() => { <button
modal_open = true; 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" 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:w-auto sm:text-sm mt-1 sm:mt-0"
{$_("laeufer-hinzufuegen")} >
</button> {$_("laeufer-hinzufuegen")}
<button </button>
on:click={() => { <button
import_modal_open = true; 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" 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:w-auto sm:text-sm mt-1 sm:mt-0"
{$_("import-runners")} >
</button> {$_("import-runners")}
{/if} </button>
</span> {/if}
<RunnersOverview bind:current_runners bind:addRunners /> <RunnersOverview bind:current_runners bind:addRunners />
</section> </section>

View File

@@ -6,7 +6,7 @@
<div class="text-center items-center justify-center"> <div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500"> <p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={runners_empty} alt="" /> <img class="w-full h-44" src={runners_empty} alt="" />
<span class="font-bold">{$_('there-are-no-runners-added-yet')}</span><br /> <span class="font-bold">{$_("there-are-no-runners-added-yet")}</span><br />
<span>{$_('add-your-first-runner')}</span> <span>{$_("add-your-first-runner")}</span>
</p> </p>
</div> </div>

View File

@@ -1,267 +1,267 @@
<script> <script>
import { import {
RunnerOrganizationService, RunnerOrganizationService,
RunnerService, RunnerService,
RunnerTeamService, RunnerTeamService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import { import {
createSvelteTable, createSvelteTable,
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
getFilteredRowModel, getFilteredRowModel,
getPaginationRowModel, getPaginationRowModel,
getSortedRowModel, getSortedRowModel,
renderComponent, renderComponent,
} from "@tanstack/svelte-table"; } from "@tanstack/svelte-table";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import store from "../../store"; import store from "../../store";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte"; import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import InputElement from "../shared/InputElement.svelte"; import InputElement from "../shared/InputElement.svelte";
import TableActions from "../shared/TableActions.svelte"; import TableActions from "../shared/TableActions.svelte";
import { groupFilter } from "../shared/tablefilters"; import { groupFilter } from "../shared/tablefilters";
import DeleteRunnerModal from "./DeleteRunnerModal.svelte"; import DeleteRunnerModal from "./DeleteRunnerModal.svelte";
import Toastify from "toastify-js"; import TableBottom from "../shared/TableBottom.svelte";
import TableBottom from "../shared/TableBottom.svelte"; import TableHeader from "../shared/TableHeader.svelte";
import TableHeader from "../shared/TableHeader.svelte";
$: selectedRunners =
$: selectedRunners = $table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$table?.getSelectedRowModel().rows.map((row) => row.original) || []; $: selected =
$: selected = $table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: active_delete = undefined;
$: active_delete = undefined; let dataLoaded = false;
let dataLoaded = false; export let current_runners = [];
export let current_runners = []; $: sponsoring_contracts_show = selected.length > 0;
$: sponsoring_contracts_show = selected.length > 0; $: cards_show = selected.length > 0;
$: cards_show = selected.length > 0; $: certificates_show = selected.length > 0;
$: certificates_show = selected.length > 0; $: teams = [];
$: teams = []; $: orgs = [];
$: orgs = [];
export const addRunners = (runners) => {
export const addRunners = (runners) => { current_runners = current_runners.concat(...runners);
current_runners = current_runners.concat(...runners); options.update((options) => ({
options.update((options) => ({ ...options,
...options, data: current_runners,
data: current_runners, }));
})); };
};
//Section table
//Section table const columns = [
const columns = [ {
{ accessorKey: "id",
accessorKey: "id", header: () => "id",
header: () => "id", filterFn: `equalsString`,
filterFn: `equalsString`, },
}, {
{ accessorKey: "firstname",
accessorKey: "firstname", header: () => $_("first-name"),
header: () => $_("first-name"), filterFn: `includesString`,
filterFn: `includesString`, },
}, {
{ accessorKey: "middlename",
accessorKey: "middlename", header: () => $_("middle-name"),
header: () => $_("middle-name"), cell: (info) => {
cell: (info) => { if (!info || !info.getValue()) {
if (!info || !info.getValue()) { return "";
return ""; }
} return info.getValue();
return info.getValue(); },
}, filterFn: `includesString`,
filterFn: `includesString`, },
}, {
{ accessorKey: "lastname",
accessorKey: "lastname", header: () => $_("last-name"),
header: () => $_("last-name"), filterFn: `includesString`,
filterFn: `includesString`, },
}, {
{ accessorKey: "group",
accessorKey: "group", header: () => $_("group"),
header: () => $_("group"), cell: (info) => {
cell: (info) => { const group = info.getValue();
const group = info.getValue(); if (group.responseType === "RUNNERORGANIZATION") {
if (group.responseType === "RUNNERORGANIZATION") { return group.name;
return group.name; }
} return `${group.parentGroup.name} > ${group.name}`;
return `${group.parentGroup.name} > ${group.name}`; },
}, filterFn: `group`,
filterFn: `group`, },
}, {
{ accessorKey: "distance",
accessorKey: "distance", header: () => $_("distance"),
header: () => $_("distance"), cell: (info) => {
cell: (info) => { if (info.getValue() < 1000) {
if (info.getValue() < 1000) { return `${info.getValue()} m`;
return `${info.getValue()} m`; }
} return `${(info.getValue() / 1000).toFixed(1)} km`;
return `${(info.getValue() / 1000).toFixed(1)} km`; },
}, enableColumnFilter: false,
enableColumnFilter: false, },
}, {
{ accessorKey: "actions",
accessorKey: "actions", header: () => $_("action"),
header: () => $_("action"), cell: (info) => {
cell: (info) => { return renderComponent(TableActions, {
return renderComponent(TableActions, { detailsLink: `./${info.row.original.id}`,
detailsLink: `./${info.row.original.id}`, deleteAction: () => {
deleteAction: () => { active_delete =
active_delete = current_runners[
current_runners[ current_runners.findIndex((r) => r.id == info.row.original.id)
current_runners.findIndex((r) => r.id == info.row.original.id) ];
]; },
}, deleteEnabled:
deleteEnabled: store.state.jwtinfo.userdetails.permissions.includes(
store.state.jwtinfo.userdetails.permissions.includes( "RUNNER:DELETE"
"RUNNER:DELETE" ),
), });
}); },
}, enableColumnFilter: false,
enableColumnFilter: false, enableSorting: false,
enableSorting: false, },
}, ];
]; const options = writable({
const options = writable({ data: [],
data: [], columns: columns,
columns: columns, filterFns: {
filterFns: { group: groupFilter,
group: groupFilter, },
}, initialState: {
initialState: { pagination: {
pagination: { pageSize: 50,
pageSize: 50, },
}, },
}, enableRowSelection: true,
enableRowSelection: true, getCoreRowModel: getCoreRowModel(),
getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(),
getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(),
getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(),
getSortedRowModel: getSortedRowModel(), });
}); const table = createSvelteTable(options);
const table = createSvelteTable(options);
async function deleteRunner(delete_runner_id) {
async function deleteRunner(delete_runner_id) { await RunnerService.runnerControllerRemove(delete_runner_id, true);
await RunnerService.runnerControllerRemove(delete_runner_id, true); current_runners = current_runners.filter((r) => r.id !== delete_runner_id);
current_runners = current_runners.filter((r) => r.id !== delete_runner_id); options.update((options) => ({
options.update((options) => ({ ...options,
...options, data: current_runners,
data: current_runners, }));
})); toast($_("runner-deleted"));
Toastify({ }
text: $_("runner-deleted"),
duration: 3500, onMount(async () => {
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
}).showToast(); teams = val;
} });
RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
onMount(async () => { (val) => {
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { orgs = val;
teams = val; }
}); );
RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => { let page = 0;
orgs = val; while (page >= 0) {
} const runners = await RunnerService.runnerControllerGetAll(page, 500);
); if (runners.length == 0) {
page = -2;
let page = 0; }
while (page >= 0) {
const runners = await RunnerService.runnerControllerGetAll(page, 1000); current_runners = current_runners.concat(...runners);
if (runners.length == 0) { options.update((options) => ({
page = -2; ...options,
} data: current_runners,
}));
current_runners = current_runners.concat(...runners);
options.update((options) => ({ dataLoaded = true;
...options, page++;
data: current_runners, }
})); });
</script>
dataLoaded = true;
page++; <DeleteRunnerModal
} delete_runner={active_delete}
console.log("All runners loaded"); modal_open={active_delete != undefined}
}); on:delete={(event) => {
</script> deleteRunner(event.detail.id);
}}
<DeleteRunnerModal />
delete_runner={active_delete} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")}
modal_open={active_delete != undefined} {#if !dataLoaded}
on:delete={(event) => { <div
deleteRunner(event.detail.id); class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
}} role="alert"
/> >
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")} <p class="font-bold">{$_("runners-are-being-loaded")}</p>
{#if !dataLoaded} <p class="text-sm">{$_("this-might-take-a-moment")}</p>
<div </div>
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" {:else}
role="alert" <div class="h-12 mt-2">
> <GenerateSponsoringContracts
<p class="font-bold">{$_("runners-are-being-loaded")}</p> bind:sponsoring_contracts_show
<p class="text-sm">{$_("this-might-take-a-moment")}</p> bind:generate_runners={selectedRunners}
</div> />
{:else} <GenerateRunnerCards
<div class="h-12 mt-2"> bind:cards_show
<GenerateSponsoringContracts bind:generate_runners={selectedRunners}
bind:sponsoring_contracts_show />
bind:generate_runners={selectedRunners} <GenerateRunnerCertificates
/> bind:certificates_show
<GenerateRunnerCards bind:generate_runners={selectedRunners}
bind:cards_show />
bind:generate_runners={selectedRunners} </div>
/> <div class="overflow-x-auto">
<GenerateRunnerCertificates <table class="w-full">
bind:certificates_show <thead class="border-b border-gray-400">
bind:generate_runners={selectedRunners} {#each $table.getHeaderGroups() as headerGroup}
/> <tr class="select-none">
</div> <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<div class="overflow-x-auto"> <InputElement
<table class="w-full"> type="checkbox"
<thead> checked={$table.getIsAllRowsSelected()}
{#each $table.getHeaderGroups() as headerGroup} indeterminate={$table.getIsSomeRowsSelected()}
<tr class="select-none"> on:change={() => $table.toggleAllRowsSelected()}
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px"> />
<InputElement </th>
type="checkbox" {#each headerGroup.headers as header}
checked={$table.getIsAllRowsSelected()} <TableHeader {header} />
indeterminate={$table.getIsSomeRowsSelected()} {/each}
on:change={() => $table.toggleAllRowsSelected()} </tr>
/> {/each}
</th> </thead>
{#each headerGroup.headers as header} <tbody>
<TableHeader {header} /> {#each $table.getRowModel().rows as row}
{/each} <tr class="odd:bg-white even:bg-gray-100">
</tr> <td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
{/each} <InputElement
</thead> type="checkbox"
<tbody> checked={row.getIsSelected()}
{#each $table.getRowModel().rows as row} on:change={() => row.toggleSelected()}
<tr> />
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px"> </td>
<InputElement {#each row.getVisibleCells() as cell}
type="checkbox" <td>
checked={row.getIsSelected()} <svelte:component
on:change={() => row.toggleSelected()} this={flexRender(
/> cell.column.columnDef.cell,
</td> cell.getContext()
{#each row.getVisibleCells() as cell} )}
<td> />
<svelte:component </td>
this={flexRender( {/each}
cell.column.columnDef.cell, </tr>
cell.getContext() {/each}
)} </tbody>
/> </table>
</td> </div>
{/each} <div class="h-2" />
</tr> {/if}
{/each} {/if}
</tbody> <TableBottom {table} {selected} />
</table>
</div> <style>
<div class="h-2" /> table tbody tr td:nth-child(2) {
{/if} font-family: monospace;
{/if} }
<TableBottom {table} {selected} /> </style>

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