Compare commits

..

130 Commits

Author SHA1 Message Date
87df34bb56 chore(release): 1.10.5
All checks were successful
Build release images / build-container (push) Successful in 1m0s
2025-04-22 17:47:22 +02:00
9c3b742a98 feat(runners): add distance sorting 2025-04-22 17:47:04 +02:00
68bf3717f9 chore(release): 1.10.4
All checks were successful
Build release images / build-container (push) Successful in 1m0s
2025-04-22 17:27:18 +02:00
f88c6e0dba fix(runners): Fix runner overview sort 2025-04-22 17:26:18 +02:00
12050cdda9 chore(release): 1.10.3
All checks were successful
Build release images / build-container (push) Successful in 1m24s
2025-04-17 21:15:58 +02:00
01e77a97f3 feat(pdf): Send selfservicelink for generation 2025-04-17 21:13:15 +02:00
10824b5d9b chore(deps): Bump @odit/lfk-client-js 2025-04-17 21:03:28 +02:00
d9870e03bc chore(release): 1.10.2
All checks were successful
Build release images / build-container (push) Successful in 1m0s
2025-04-11 12:24:49 +02:00
785b9e0b60 refactor(runners): filter table for created_via 2025-04-11 12:24:02 +02:00
fce2bc645e chore(release): 1.10.1
All checks were successful
Build release images / build-container (push) Successful in 1m1s
2025-04-09 11:57:16 +02:00
991716a7f5 feat: runner list filtered by created_via 2025-04-09 11:56:59 +02:00
aa720f2460 chore(release): 1.10.0
All checks were successful
Build release images / build-container (push) Successful in 1m1s
2025-04-09 10:39:06 +02:00
ac4ef8fb6d feat: working CardAssignment 2025-04-09 10:38:45 +02:00
eae0afda23 chore(release): 1.9.11
All checks were successful
Build release images / build-container (push) Successful in 57s
2025-04-08 22:17:04 +02:00
5291f8a4d1 feat(dash): add runnersViaKiosk 2025-04-08 22:16:46 +02:00
e2d7de1e9e chore(release): 1.9.10
All checks were successful
Build release images / build-container (push) Successful in 1m0s
2025-04-08 21:57:18 +02:00
d7c9c27ec7 feat: add experimental ui for mobile card assignment 2025-04-08 21:56:23 +02:00
153b1b3c2b chore(release): 1.9.9
All checks were successful
Build release images / build-container (push) Successful in 55s
2025-04-04 22:40:24 +02:00
ec63c7c1c5 fix(CopyScanStationTokenModal): code sizes 2025-04-04 22:40:04 +02:00
05c2535698 chore(release): 1.9.8
All checks were successful
Build release images / build-container (push) Successful in 57s
2025-04-02 22:04:18 +02:00
e261d5e345 feat(GenerateSponsoringContracts): show download progress
commit c3226c37c9
Author: Philipp Dormann <philipp@philippdormann.de>
Date:   Wed Apr 2 22:03:06 2025 +0200

    wip

commit 210140fd67
Author: Philipp Dormann <philipp@philippdormann.de>
Date:   Wed Apr 2 21:58:07 2025 +0200

    wip

commit 35e58d233e
Author: Philipp Dormann <philipp@philippdormann.de>
Date:   Wed Apr 2 21:55:21 2025 +0200

    wip

commit a09bf31e22
Author: Philipp Dormann <philipp@philippdormann.de>
Date:   Wed Apr 2 21:48:51 2025 +0200

    wip

commit 7c31fba83a
Author: Philipp Dormann <philipp@philippdormann.de>
Date:   Wed Apr 2 21:48:37 2025 +0200

    wip
2025-04-02 22:03:47 +02:00
c00497d776 chore(release): 1.9.7
All checks were successful
Build release images / build-container (push) Successful in 57s
2025-04-02 13:35:25 +02:00
766eeab49f fix: ImportRunnerModal scrolling & team select 2025-04-02 13:35:08 +02:00
3c9b404234 chore(release): 1.9.6
All checks were successful
Build release images / build-container (push) Successful in 56s
2025-03-29 11:28:13 +01:00
9c56b3883e pnpm allow builds 2025-03-29 11:27:46 +01:00
3d506db975 chore(release): 1.9.5
All checks were successful
Build release images / build-container (push) Successful in 56s
2025-03-29 02:23:51 +01:00
d7e84a79a8 feat: modal improvements 2025-03-29 02:23:32 +01:00
102471eaaa chore(release): 1.9.4
All checks were successful
Build release images / build-container (push) Successful in 54s
2025-03-29 02:00:45 +01:00
90b0fec236 feat: improve modals 2025-03-29 02:00:27 +01:00
4883e179e7 chore(release): 1.9.3
All checks were successful
Build release images / build-container (push) Successful in 55s
2025-03-29 01:45:52 +01:00
13c6e96292 feat: modal improvements 2025-03-29 01:45:25 +01:00
f547c0cc81 feat(OrgDetail): improve selfservice link copy 2025-03-29 01:42:45 +01:00
fbe38eede9 feat: modal improvements 2025-03-29 01:34:46 +01:00
22551c379f feat: modal improvements 2025-03-29 01:14:17 +01:00
a102af5a78 fix: sidebar 2025-03-29 00:31:58 +01:00
e9dffcea83 ci: only tagged runs for now 2025-03-28 22:02:37 +01:00
b9563d75dd chore(deps): pnpm@10.7 2025-03-28 22:02:11 +01:00
3a569422ad chore(release): 1.9.2
All checks were successful
Build Latest and dev images / build-container (push) Successful in 1m9s
Build release images / build-container (push) Successful in 1m9s
2025-03-28 22:01:15 +01:00
0ee43f80a6 feat(dashboard): show runners via selfservice count 2025-03-28 22:01:02 +01:00
f4542adf3b chore: update lfk client 2025-03-28 22:00:43 +01:00
9f0623d194 refactor: change release message 2025-03-28 22:00:30 +01:00
5bab95a942 🚀RELEASE v1.9.1
All checks were successful
Build release images / build-container (push) Successful in 1m14s
Build Latest and dev images / build-container (push) Successful in 1m17s
2025-03-28 21:26:56 +01:00
831f36946d feat(ConfirmTeamDeletionModal): success toast
All checks were successful
Build Latest and dev images / build-container (push) Successful in 59s
2025-03-28 21:18:28 +01:00
a4fbabaf9a feat(RunnerDetail): show created_via 2025-03-28 18:41:28 +01:00
04897c7d2e refactor: project cleanup
All checks were successful
Build Latest and dev images / build-container (push) Successful in 56s
2025-03-28 17:25:31 +01:00
b7e6fdaeac 🚀RELEASE v1.9.0
Some checks failed
Build Latest and dev images / build-container (push) Failing after 14s
Build release images / build-container (push) Successful in 57s
2025-03-28 11:52:13 +01:00
481f6b686e feat: improve toasts
All checks were successful
Build Latest and dev images / build-container (push) Successful in 1m0s
2025-03-28 11:44:10 +01:00
e7a69ebdca feat: improve modals 2025-03-28 11:40:35 +01:00
194c3c4886 feat: improved track deletion ui feedback 2025-03-28 11:37:47 +01:00
f5a46aa203 feat: modal improvements 2025-03-28 11:37:06 +01:00
443371e2fd feat: improve modals 2025-03-28 11:29:30 +01:00
d7ab9247cd feat: improve translations 2025-03-28 11:26:52 +01:00
a2cd54fba4 feat: improve ConfirmTeamDeletionModal
All checks were successful
Build Latest and dev images / build-container (push) Successful in 1m0s
2025-03-28 11:16:44 +01:00
9048f3df77 feat: improve translations 2025-03-28 11:15:12 +01:00
fecf3b59a3 feat: improved ConfirmOrgDeletionModal
All checks were successful
Build Latest and dev images / build-container (push) Successful in 1m2s
2025-03-28 11:11:21 +01:00
18a4623e71 feat: improved translations 2025-03-28 11:11:08 +01:00
968a7ccc0e feat: improved sidebar z-index 2025-03-28 11:10:54 +01:00
6249502a88 🚀RELEASE v1.8.2
All checks were successful
Build Latest and dev images / build-container (push) Successful in 1m1s
Build release images / build-container (push) Successful in 56s
2025-03-26 22:02:25 +01:00
8a78034079 feat(dashboard): active item for teams + runners
All checks were successful
Build Latest and dev images / build-container (push) Successful in 57s
2025-03-26 22:01:18 +01:00
7633b7b056 feat: improvement of card,certificate,sponsoringcontract action buttons
All checks were successful
Build Latest and dev images / build-container (push) Successful in 1m1s
2025-03-26 21:59:55 +01:00
b8e6b24bf3 🚀RELEASE v1.8.1
All checks were successful
Build release images / build-container (push) Successful in 1m18s
Build Latest and dev images / build-container (push) Successful in 1m21s
2025-03-26 19:12:05 +01:00
97b7ca931f fix(pdf_generation): Only load direct runners for direct calls 2025-03-26 19:10:37 +01:00
48dd9acde5 🚀RELEASE v1.8.0
All checks were successful
Build release images / build-container (push) Successful in 1m13s
Build Latest and dev images / build-container (push) Successful in 1m15s
2025-03-26 16:03:19 +01:00
5147a20b3c fix(DonorDetail): donor deletion
All checks were successful
Build Latest and dev images / build-container (push) Successful in 1m20s
2025-03-26 15:57:44 +01:00
bb2319a78d chore(deps): bump
Some checks failed
Build Latest and dev images / build-container (push) Failing after 23s
2025-03-26 15:35:53 +01:00
7c10d95c1c feat(RunnerOrganizationService.runnerOrganizationControllerGetRunners): load all runners in org
All checks were successful
Build Latest and dev images / build-container (push) Successful in 1m7s
2025-03-26 15:34:38 +01:00
f734d1e3f6 feat: cleanup TeamDetail + OrgDetail 2025-03-26 15:33:34 +01:00
e567bb35c3 fix(ci): Correct tag pattern syntax in release workflow
All checks were successful
Build Latest and dev images / build-container (push) Successful in 58s
2025-03-22 22:39:20 +01:00
3ec18a6964 refactor(ci): Add Gitea workflow for building release images and remove Woodpecker configuration
All checks were successful
Build Latest and dev images / build-container (push) Successful in 1m53s
2025-03-22 22:38:43 +01:00
847fa288f1 refactor(ci): Switch to actions for dev 2025-03-22 22:33:59 +01:00
824ecfab2e wip 2025-03-20 22:29:36 +01:00
5f5d8277b9 wip 2025-03-20 22:15:36 +01:00
0a6cf619b0 wip 2025-03-20 22:13:41 +01:00
050a146ae0 wip 2025-03-20 21:52:39 +01:00
1bc53146b9 wip 2025-03-20 21:51:00 +01:00
e82350df4a wip 2025-03-20 21:42:02 +01:00
3d3ce2918b wip 2025-03-20 21:34:51 +01:00
79e6a4212d feat: improve input readability 2025-03-20 21:31:33 +01:00
37cdbba0a3 wip 2025-03-20 21:31:03 +01:00
c37fb98bed feat: improve fonts + button positions 2025-03-20 21:29:10 +01:00
975f145444 feat(dashboard): full width for sidebar items 2025-03-18 00:46:29 +01:00
391186d01f feat: athiti font 2025-03-18 00:46:10 +01:00
ae056cd88c 🚀RELEASE v1.7.0
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
2024-12-17 17:41:11 +01:00
7f989b206b fix(pdfgeneration): Added parent_group
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-12-17 17:40:19 +01:00
65ce02e777 refactor(cards): Switched over to new document-server api
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2024-12-17 17:32:33 +01:00
878d9714cb refactor(pdfgeneration): Switch to new document-server api
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-12-17 17:29:42 +01:00
f99b7f4bb8 refactor(pdfgeneration): Switched contract generation over to new document-server
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2024-12-17 17:19:20 +01:00
e23098410c refactor(pdfgeneration): Switch cards over to new service 2024-12-17 17:09:55 +01:00
04494d2a2a chore: bump
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
2024-12-11 17:30:49 +01:00
e2d6fbb513 refactor(orgs): Swtich to new selfservice baseurl 2024-12-11 17:29:14 +01:00
477c650f3f 🚀RELEASE v1.5.3
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
2024-11-26 19:20:04 +01:00
fc15c68cba feat(dx): Yarn support
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-11-26 19:19:31 +01:00
32b5f5420b refactor(ci): Only build licences, don't export
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2024-11-26 19:11:14 +01:00
ee87f82799 fix(ci): Update git pushb settings
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-11-26 19:06:32 +01:00
7c08f522aa fix(ci): Update relase machanism 2024-11-26 19:06:16 +01:00
e211554579 fix(ci): Install pnpm
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-11-26 19:01:51 +01:00
7ba890dfd7 fix(ci): Switch over to new woodpecker version
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-11-26 18:59:42 +01:00
68b4309164 chore(deps): bump some 2024-11-21 18:05:11 +01:00
d803f3d490 fix: unexpected/ missing props 2024-11-21 17:05:06 +01:00
5468766d87 fix(orgs): ImportRunnerModal props 2024-11-21 17:00:03 +01:00
e967d8d20c feat(dashboard): reorder menu items 2024-11-21 16:59:48 +01:00
ad4db882f0 feat: cleanup random page toasts 2024-11-21 16:56:10 +01:00
84aa846b87 feat(about): cleanup ui 2024-11-21 16:55:58 +01:00
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
6193eff38e new license file version [CI SKIP] 2023-05-10 11:29:03 +00:00
126 changed files with 15667 additions and 12648 deletions

View File

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

View File

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

View File

@@ -1,2 +1,4 @@
public/env.sample.js
.pnpm-store
.pnpm-store
.yarn
.pnp.*

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

@@ -0,0 +1,33 @@
name: Build release images
on:
push:
tags:
- "*.*.*"
jobs:
build-container:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 19
- run: npm i -g pnpm@10.7 && pnpm i
- run: pnpm licenses:export
- name: Login to registry
uses: docker/login-action@v3
with:
registry: registry.odit.services
username: ${{ vars.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: |
${{ vars.REGISTRY }}/lfk/frontend:${{ github.ref_name }}
platforms: linux/amd64,linux/arm64

4
.gitignore vendored
View File

@@ -3,4 +3,6 @@ node_modules
public/env.js
public/index.html
/dist
.pnpm-store
.pnpm-store
.yarn
.pnp.*

View File

@@ -2,8 +2,277 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.10.5](https://git.odit.services/lfk/frontend/compare/1.10.4...1.10.5)
- feat(runners): add distance sorting [`9c3b742`](https://git.odit.services/lfk/frontend/commit/9c3b742a98e17837fbcabe308fb2774dc13b03b8)
#### [1.10.4](https://git.odit.services/lfk/frontend/compare/1.10.3...1.10.4)
> 22 April 2025
- chore(release): 1.10.4 [`68bf371`](https://git.odit.services/lfk/frontend/commit/68bf3717f9cf15a547c51e245452d4e013f7acad)
- fix(runners): Fix runner overview sort [`f88c6e0`](https://git.odit.services/lfk/frontend/commit/f88c6e0dbab620887e180de0d3eab03b8215a477)
#### [1.10.3](https://git.odit.services/lfk/frontend/compare/1.10.2...1.10.3)
> 17 April 2025
- feat(pdf): Send selfservicelink for generation [`01e77a9`](https://git.odit.services/lfk/frontend/commit/01e77a97f3f28700e0249d35afd9641b56d9c55d)
- chore(release): 1.10.3 [`12050cd`](https://git.odit.services/lfk/frontend/commit/12050cdda95b5f8eb5b414b81655d06faae3ef1e)
- chore(deps): Bump @odit/lfk-client-js [`10824b5`](https://git.odit.services/lfk/frontend/commit/10824b5d9b207e14a37fa23e90d54337d76e60a9)
#### [1.10.2](https://git.odit.services/lfk/frontend/compare/1.10.1...1.10.2)
> 11 April 2025
- refactor(runners): filter table for created_via [`785b9e0`](https://git.odit.services/lfk/frontend/commit/785b9e0b60a9961f99d0c519d6bb12dc735ac605)
- chore(release): 1.10.2 [`d9870e0`](https://git.odit.services/lfk/frontend/commit/d9870e03bc3175ee9b299174a19f257d6046a718)
#### [1.10.1](https://git.odit.services/lfk/frontend/compare/1.10.0...1.10.1)
> 9 April 2025
- feat: runner list filtered by created_via [`991716a`](https://git.odit.services/lfk/frontend/commit/991716a7f55d0414111ad264ad1e93de9e82971a)
- chore(release): 1.10.1 [`fce2bc6`](https://git.odit.services/lfk/frontend/commit/fce2bc645e040322f4d1b98a1ed1ab5df7227b6d)
#### [1.10.0](https://git.odit.services/lfk/frontend/compare/1.9.11...1.10.0)
> 9 April 2025
- feat: working CardAssignment [`ac4ef8f`](https://git.odit.services/lfk/frontend/commit/ac4ef8fb6ded5c5d5678a651420e356b3ef45399)
- chore(release): 1.10.0 [`aa720f2`](https://git.odit.services/lfk/frontend/commit/aa720f2460079e35eed9d87a2ac580a3305efbd5)
#### [1.9.11](https://git.odit.services/lfk/frontend/compare/1.9.10...1.9.11)
> 8 April 2025
- feat(dash): add runnersViaKiosk [`5291f8a`](https://git.odit.services/lfk/frontend/commit/5291f8a4d1721cd0c745191ebc85f221c34a23c8)
- chore(release): 1.9.11 [`eae0afd`](https://git.odit.services/lfk/frontend/commit/eae0afda23a54020e25821c0188d8cbec3139593)
#### [1.9.10](https://git.odit.services/lfk/frontend/compare/1.9.9...1.9.10)
> 8 April 2025
- feat: add experimental ui for mobile card assignment [`d7c9c27`](https://git.odit.services/lfk/frontend/commit/d7c9c27ec7a1fea1cbaf26914843d044bbae32fe)
- chore(release): 1.9.10 [`e2d7de1`](https://git.odit.services/lfk/frontend/commit/e2d7de1e9e1fd134f54876fa80f19f94fbea3672)
#### [1.9.9](https://git.odit.services/lfk/frontend/compare/1.9.8...1.9.9)
> 4 April 2025
- chore(release): 1.9.9 [`153b1b3`](https://git.odit.services/lfk/frontend/commit/153b1b3c2badee4826be614c3dbaafc10e1fbfea)
- fix(CopyScanStationTokenModal): code sizes [`ec63c7c`](https://git.odit.services/lfk/frontend/commit/ec63c7c1c51ccaf25bdd1eacffda66c820003a4c)
#### [1.9.8](https://git.odit.services/lfk/frontend/compare/1.9.7...1.9.8)
> 2 April 2025
- feat(GenerateSponsoringContracts): show download progress [`e261d5e`](https://git.odit.services/lfk/frontend/commit/e261d5e345f3175672bf86646ed838dd23400e50)
- chore(release): 1.9.8 [`05c2535`](https://git.odit.services/lfk/frontend/commit/05c253569877a45f3c4759262255ca70aa9ee4a3)
#### [1.9.7](https://git.odit.services/lfk/frontend/compare/1.9.6...1.9.7)
> 2 April 2025
- fix: ImportRunnerModal scrolling & team select [`766eeab`](https://git.odit.services/lfk/frontend/commit/766eeab49fb3ca5715c19dbf9bc53cb71124d3df)
- chore(release): 1.9.7 [`c00497d`](https://git.odit.services/lfk/frontend/commit/c00497d7760a935965cc83213f72f35999a3c168)
#### [1.9.6](https://git.odit.services/lfk/frontend/compare/1.9.5...1.9.6)
> 29 March 2025
- chore(release): 1.9.6 [`3c9b404`](https://git.odit.services/lfk/frontend/commit/3c9b404234c7d7d2f0c48256be2130a0ed8ae047)
- pnpm allow builds [`9c56b38`](https://git.odit.services/lfk/frontend/commit/9c56b3883eeab9e1a5e1c4921bfb6528c230e0d4)
#### [1.9.5](https://git.odit.services/lfk/frontend/compare/1.9.4...1.9.5)
> 29 March 2025
- feat: modal improvements [`d7e84a7`](https://git.odit.services/lfk/frontend/commit/d7e84a79a892294d532cc93aa3391c14a7a5ce99)
- chore(release): 1.9.5 [`3d506db`](https://git.odit.services/lfk/frontend/commit/3d506db97502399e8b381b4cf38af2f07a584aec)
#### [1.9.4](https://git.odit.services/lfk/frontend/compare/1.9.3...1.9.4)
> 29 March 2025
- feat: improve modals [`90b0fec`](https://git.odit.services/lfk/frontend/commit/90b0fec2366b608d163decdcd8798e879cf8218d)
- chore(release): 1.9.4 [`102471e`](https://git.odit.services/lfk/frontend/commit/102471eaaae390d3ef815afde9ac4081be7d5dbc)
#### [1.9.3](https://git.odit.services/lfk/frontend/compare/1.9.2...1.9.3)
> 29 March 2025
- feat: modal improvements [`fbe38ee`](https://git.odit.services/lfk/frontend/commit/fbe38eede95813e163a390b693790d78ce75c215)
- feat: modal improvements [`22551c3`](https://git.odit.services/lfk/frontend/commit/22551c379f704b0d9c28c499f7d3f5a37f1533ca)
- ci: only tagged runs for now [`e9dffce`](https://git.odit.services/lfk/frontend/commit/e9dffcea835cbcd6b5eb4ed1cc3feb62a9e831db)
- chore(release): 1.9.3 [`4883e17`](https://git.odit.services/lfk/frontend/commit/4883e179e7090cf90783dcdecd5df8a422880188)
- feat: modal improvements [`13c6e96`](https://git.odit.services/lfk/frontend/commit/13c6e96292613d9619f779f2557201cf0b938753)
- feat(OrgDetail): improve selfservice link copy [`f547c0c`](https://git.odit.services/lfk/frontend/commit/f547c0cc817d7db0c70df4059dad753e9b16c1c9)
- chore(deps): pnpm@10.7 [`b9563d7`](https://git.odit.services/lfk/frontend/commit/b9563d75dd15519d9ec5d425d628d232e7609913)
- fix: sidebar [`a102af5`](https://git.odit.services/lfk/frontend/commit/a102af5a78c83cd54b4981bff2f6c8d54cf8c74c)
#### [1.9.2](https://git.odit.services/lfk/frontend/compare/1.9.1...1.9.2)
> 28 March 2025
- chore: update lfk client [`f4542ad`](https://git.odit.services/lfk/frontend/commit/f4542adf3b7c757d907c979b989450b64553d750)
- feat(dashboard): show runners via selfservice count [`0ee43f8`](https://git.odit.services/lfk/frontend/commit/0ee43f80a65bb5b83d51d6c098bd203bc09e2f1f)
- chore(release): 1.9.2 [`3a56942`](https://git.odit.services/lfk/frontend/commit/3a569422ad7d68d0009fa73229dd73ee00be87a9)
- refactor: change release message [`9f0623d`](https://git.odit.services/lfk/frontend/commit/9f0623d194a7784d4ede3cb6a6cd10d0aea4a180)
#### [1.9.1](https://git.odit.services/lfk/frontend/compare/1.9.0...1.9.1)
> 28 March 2025
- refactor: project cleanup [`04897c7`](https://git.odit.services/lfk/frontend/commit/04897c7d2e89cb7e834815907409698ad6758637)
- 🚀RELEASE v1.9.1 [`5bab95a`](https://git.odit.services/lfk/frontend/commit/5bab95a9423d9da8c17165732b988ca868f950a5)
- feat(RunnerDetail): show created_via [`a4fbaba`](https://git.odit.services/lfk/frontend/commit/a4fbabaf9a0a9a26b6c6782056f11b8a646b8f16)
- feat(ConfirmTeamDeletionModal): success toast [`831f369`](https://git.odit.services/lfk/frontend/commit/831f36946d5db777ca77855161f653f861cbd56e)
#### [1.9.0](https://git.odit.services/lfk/frontend/compare/1.8.2...1.9.0)
> 28 March 2025
- feat: improved ConfirmOrgDeletionModal [`fecf3b5`](https://git.odit.services/lfk/frontend/commit/fecf3b59a320afafee52c95b361edec644c5cbff)
- feat: modal improvements [`f5a46aa`](https://git.odit.services/lfk/frontend/commit/f5a46aa203ca2adf2e4e6fe4863629ca80f1becb)
- feat: improve ConfirmTeamDeletionModal [`a2cd54f`](https://git.odit.services/lfk/frontend/commit/a2cd54fba4a987f7f7dbab22cc958f9aea2817ff)
- feat: improve modals [`443371e`](https://git.odit.services/lfk/frontend/commit/443371e2fdc42506e6e87379bd65facbd8f22d7d)
- feat: improve toasts [`481f6b6`](https://git.odit.services/lfk/frontend/commit/481f6b686e77ffa36ee08b62f653d626dd9124c9)
- feat: improve modals [`e7a69eb`](https://git.odit.services/lfk/frontend/commit/e7a69ebdca668a5d78b52d092aa1bca6259aa19b)
- 🚀RELEASE v1.9.0 [`b7e6fda`](https://git.odit.services/lfk/frontend/commit/b7e6fdaeacf17a7cc77109460b5e2c6d3775ef7b)
- feat: improve translations [`d7ab924`](https://git.odit.services/lfk/frontend/commit/d7ab9247cd2eab4f7269b23de5fada76a99ac8bc)
- feat: improved translations [`18a4623`](https://git.odit.services/lfk/frontend/commit/18a4623e71dfd942f2268203ce713030acfb2d9d)
- feat: improve translations [`9048f3d`](https://git.odit.services/lfk/frontend/commit/9048f3df774df233705a41b08012193447eab803)
- feat: improved sidebar z-index [`968a7cc`](https://git.odit.services/lfk/frontend/commit/968a7ccc0e7917bf1a42ac8f85f358880951cc2a)
- feat: improved track deletion ui feedback [`194c3c4`](https://git.odit.services/lfk/frontend/commit/194c3c4886e3f3206d76d8634be9d3dd2fa02d8d)
#### [1.8.2](https://git.odit.services/lfk/frontend/compare/1.8.1...1.8.2)
> 26 March 2025
- feat: improvement of card,certificate,sponsoringcontract action buttons [`7633b7b`](https://git.odit.services/lfk/frontend/commit/7633b7b05671342bc30e0bbecbcd9450e06b5e4d)
- 🚀RELEASE v1.8.2 [`6249502`](https://git.odit.services/lfk/frontend/commit/6249502a88ec5bfba6dfbc3ad5ede82d71d0d9e2)
- feat(dashboard): active item for teams + runners [`8a78034`](https://git.odit.services/lfk/frontend/commit/8a780340792445fff1f78db994fb78acb5da8304)
#### [1.8.1](https://git.odit.services/lfk/frontend/compare/1.8.0...1.8.1)
> 26 March 2025
- 🚀RELEASE v1.8.1 [`b8e6b24`](https://git.odit.services/lfk/frontend/commit/b8e6b24bf32379c3f4a1679d422e6fdcc45f7c99)
- fix(pdf_generation): Only load direct runners for direct calls [`97b7ca9`](https://git.odit.services/lfk/frontend/commit/97b7ca931f607ee64509ad10c2269632fc691091)
#### [1.8.0](https://git.odit.services/lfk/frontend/compare/1.7.0...1.8.0)
> 26 March 2025
- wip [`824ecfa`](https://git.odit.services/lfk/frontend/commit/824ecfab2e976cd7c6cd2851be8a9be5c6b686e1)
- wip [`0a6cf61`](https://git.odit.services/lfk/frontend/commit/0a6cf619b09be837d5503f4695250c7edaeeaff5)
- feat: improve fonts + button positions [`c37fb98`](https://git.odit.services/lfk/frontend/commit/c37fb98bed377744981e927ea8d22db9e20c55ca)
- 🚀RELEASE v1.8.0 [`48dd9ac`](https://git.odit.services/lfk/frontend/commit/48dd9acde595b882630855d5e6af3cfa18fc9ecf)
- wip [`1bc5314`](https://git.odit.services/lfk/frontend/commit/1bc53146b9f024f3cab613b227d29304d687c92b)
- wip [`e82350d`](https://git.odit.services/lfk/frontend/commit/e82350df4af082d2bbb322658c6c022d83b819ae)
- wip [`37cdbba`](https://git.odit.services/lfk/frontend/commit/37cdbba0a3563875e19bee560f2cd5c8fc2d7a6e)
- feat: improve input readability [`79e6a42`](https://git.odit.services/lfk/frontend/commit/79e6a4212d06029766d0a853686ed97879ebd349)
- wip [`5f5d827`](https://git.odit.services/lfk/frontend/commit/5f5d8277b98363ef15a92621fca0a209345aca95)
- chore(deps): bump [`bb2319a`](https://git.odit.services/lfk/frontend/commit/bb2319a78d253a2d6239a0d3daedc90fd29abdd0)
- feat: cleanup TeamDetail + OrgDetail [`f734d1e`](https://git.odit.services/lfk/frontend/commit/f734d1e3f643a500a6432a389c3103045cc51262)
- refactor(ci): Switch to actions for dev [`847fa28`](https://git.odit.services/lfk/frontend/commit/847fa288f1b5bbc422cc2944bbe66e80c5a00407)
- refactor(ci): Add Gitea workflow for building release images and remove Woodpecker configuration [`3ec18a6`](https://git.odit.services/lfk/frontend/commit/3ec18a696435ada26bf2de2220b190dc630a9759)
- feat: athiti font [`391186d`](https://git.odit.services/lfk/frontend/commit/391186d01f3b96638a3569dc2843bf181dc3f02c)
- fix(DonorDetail): donor deletion [`5147a20`](https://git.odit.services/lfk/frontend/commit/5147a20b3c4a46968482b1e3517047351c94f77e)
- feat(dashboard): full width for sidebar items [`975f145`](https://git.odit.services/lfk/frontend/commit/975f145444e5a478524ea2cbbfb9059b93617185)
- wip [`3d3ce29`](https://git.odit.services/lfk/frontend/commit/3d3ce2918bc20cf1080a2b5153ddd8aaf51374b4)
- feat(RunnerOrganizationService.runnerOrganizationControllerGetRunners): load all runners in org [`7c10d95`](https://git.odit.services/lfk/frontend/commit/7c10d95c1c68f4842fd323698e004a5ebf2c96cf)
- wip [`050a146`](https://git.odit.services/lfk/frontend/commit/050a146ae070d67d8308db4b9612fd6eacbb9923)
- fix(ci): Correct tag pattern syntax in release workflow [`e567bb3`](https://git.odit.services/lfk/frontend/commit/e567bb35c3b3f6eb73a2f0bc72f601e70f881ac8)
#### [1.7.0](https://git.odit.services/lfk/frontend/compare/1.6.0...1.7.0)
> 17 December 2024
- refactor(pdfgeneration): Switch cards over to new service [`e230984`](https://git.odit.services/lfk/frontend/commit/e23098410c7d0b326cdbbb3a4b63fed10611e252)
- refactor(pdfgeneration): Switch to new document-server api [`878d971`](https://git.odit.services/lfk/frontend/commit/878d9714cbc0a60cfd96bd1faf8af6af46e6fb5e)
- refactor(pdfgeneration): Switched contract generation over to new document-server [`f99b7f4`](https://git.odit.services/lfk/frontend/commit/f99b7f4bb8f166bb966022ddd10689c082d248f0)
- refactor(cards): Switched over to new document-server api [`65ce02e`](https://git.odit.services/lfk/frontend/commit/65ce02e777e6e9b3cfed248de680e5f292b3a639)
- 🚀RELEASE v1.7.0 [`ae056cd`](https://git.odit.services/lfk/frontend/commit/ae056cd88cb27f003845fa4534553cde841c7f99)
- fix(pdfgeneration): Added parent_group [`7f989b2`](https://git.odit.services/lfk/frontend/commit/7f989b206b16e2687d01a38da8e3ea9be0a52ba5)
#### [1.6.0](https://git.odit.services/lfk/frontend/compare/1.5.3...1.6.0)
> 11 December 2024
- refactor(orgs): Swtich to new selfservice baseurl [`e2d6fbb`](https://git.odit.services/lfk/frontend/commit/e2d6fbb513dc9fe7ce05855edb4b0b4b5daeb07a)
- chore: bump [`04494d2`](https://git.odit.services/lfk/frontend/commit/04494d2a2a542f25f785f3bb23e49e5eb0691c0a)
#### [1.5.3](https://git.odit.services/lfk/frontend/compare/1.5.2...1.5.3)
> 26 November 2024
- feat(dx): Yarn support [`fc15c68`](https://git.odit.services/lfk/frontend/commit/fc15c68cba0d1986563eaf63da3a68784a685a9e)
- feat(about): cleanup ui [`84aa846`](https://git.odit.services/lfk/frontend/commit/84aa846b87186b52a2f8632724d4f2cb70af062b)
- feat(dashboard): reorder menu items [`e967d8d`](https://git.odit.services/lfk/frontend/commit/e967d8d20c6972b64b0096594a09043553e9c7e5)
- fix: unexpected/ missing props [`d803f3d`](https://git.odit.services/lfk/frontend/commit/d803f3d4905d6f792b77d17025467ac13c29068b)
- chore(deps): bump some [`68b4309`](https://git.odit.services/lfk/frontend/commit/68b4309164eac40b6fda969b60a7e238985d49f8)
- 🚀RELEASE v1.5.3 [`477c650`](https://git.odit.services/lfk/frontend/commit/477c650f3f6dd2eadf5f1cc404e8fc9b02a7841b)
- fix(ci): Switch over to new woodpecker version [`7ba890d`](https://git.odit.services/lfk/frontend/commit/7ba890dfd7ba908ebef0338f6faa5e7d804cb5ef)
- refactor(ci): Only build licences, don't export [`32b5f54`](https://git.odit.services/lfk/frontend/commit/32b5f5420bf9ff656b713d61b3a0113b9d6cb69f)
- feat: cleanup random page toasts [`ad4db88`](https://git.odit.services/lfk/frontend/commit/ad4db882f0f4d00a80ae5e0072e09c071c07ffa2)
- fix(ci): Update git pushb settings [`ee87f82`](https://git.odit.services/lfk/frontend/commit/ee87f82799ce559fd43d671ab412f2643eafeac6)
- fix(ci): Update relase machanism [`7c08f52`](https://git.odit.services/lfk/frontend/commit/7c08f522aa4b2986544a4c0e5d3261c4c7296121)
- fix(ci): Install pnpm [`e211554`](https://git.odit.services/lfk/frontend/commit/e211554579b1f27d13194eff4aad76f6f030141e)
- fix(orgs): ImportRunnerModal props [`5468766`](https://git.odit.services/lfk/frontend/commit/5468766d875a6278f01ed1fd9573688374befdd5)
#### [1.5.2](https://git.odit.services/lfk/frontend/compare/1.5.1...1.5.2)
> 21 November 2024
- 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)
- 🚀RELEASE v1.5.2 [`3532968`](https://git.odit.services/lfk/frontend/commit/3532968b3399b985b1ed28ba6b89a13f35f9289b)
- 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)

View File

@@ -1,9 +1,9 @@
FROM registry.odit.services/hub/library/node:20.0.0-alpine3.17 as build
FROM registry.odit.services/hub/library/node:23.10.0-alpine3.21 AS build
ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app
COPY package.json pnpm-lock.yaml vite.config.js tailwind.config.js postcss.config.cjs index.html ./
RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8
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@10.7
RUN mkdir /pnpm && pnpm config set store-dir /pnpm && pnpm i
COPY src ./src
@@ -11,6 +11,6 @@ COPY public ./public
RUN pnpm build
# 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 ./nginx.conf /etc/nginx/nginx.conf

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
@@ -13,7 +13,7 @@
<body>
<span style="display: none; visibility: hidden" id="buildinfo"
>RELEASE_INFO-1.4.12-RELEASE_INFO</span
>RELEASE_INFO-1.10.5-RELEASE_INFO</span
>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script>

View File

@@ -1,6 +1,6 @@
{
"name": "@odit/lfk-frontend",
"version": "1.4.12",
"version": "1.10.5",
"type": "module",
"scripts": {
"i18n-order": "node order.js",
@@ -14,25 +14,25 @@
"devDependencies": {
"@odit/license-exporter": "0.2.0",
"@sveltejs/vite-plugin-svelte": "2.1.1",
"auto-changelog": "2.4.0",
"autoprefixer": "10.4.14",
"postcss": "8.4.23",
"prettier": "2.8.8",
"prettier-plugin-svelte": "2.10.0",
"release-it": "15.10.3",
"auto-changelog": "2.5.0",
"autoprefixer": "10.4.21",
"postcss": "8.5.3",
"prettier": "3.5.3",
"prettier-plugin-svelte": "3.3.3",
"release-it": "17.10.0",
"svelte-select": "3.17.0",
"tailwindcss": "3.3.2",
"tailwindcss": "3.4.15",
"vite": "4.3.3"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀RELEASE v${version}",
"commitMessage": "chore(release): ${version}",
"push": true,
"tag": true,
"tagName": null,
"tagAnnotation": "v${version}"
"tagName": "${version}",
"tagAnnotation": "${version}"
},
"npm": {
"publish": false
@@ -42,19 +42,21 @@
}
},
"dependencies": {
"@odit/lfk-client-js": "1.1.2",
"@paralleldrive/cuid2": "2.2.0",
"@fontsource/athiti": "^5.2.5",
"@odit/lfk-client-js": "1.2.4",
"@paralleldrive/cuid2": "2.2.2",
"@tanstack/svelte-table": "8.9.1",
"bwip-js": "3.4.0",
"check-password-strength": "2.0.7",
"check-password-strength": "2.0.10",
"csvtojson": "2.0.10",
"html5-qrcode": "^2.3.8",
"localforage": "1.10.0",
"marked": "4.3.0",
"svelte": "3.58.0",
"svelte-french-toast": "1.0.4-beta.0",
"svelte-french-toast": "1.2.0",
"svelte-i18n": "3.6.0",
"tinro": "0.6.12",
"validator": "13.9.0",
"validator": "13.15.0",
"xlsx": "0.18.5"
},
"volta": {

5591
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

3
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,3 @@
onlyBuiltDependencies:
- es5-ext
- esbuild

BIN
public/beep.mp3 Normal file

Binary file not shown.

View File

@@ -1,5 +1,6 @@
const config = {
baseurl: "http://localhost:4010",
baseurl_selfservice: "http://localhost:5174",
baseurl_documentserver: "http://localhost:4010/documents",
documentserver_key:
"NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe",
@@ -8,3 +9,4 @@ const config = {
default_password: "demo",
prefersHashRouting: true,
};
window.config = config;

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98.1 118">
<path fill="#ff3e00" d="M91.8 15.6C80.9-.1 59.2-4.7 43.6 5.2L16.1 22.8A31.25 31.25 0 001.9 43.9c-1.3 7.3-.2 14.8 3.3 21.3-2.4 3.6-4 7.6-4.7 11.8-1.6 8.9.5 18.1 5.7 25.4 11 15.7 32.6 20.3 48.2 10.4l27.5-17.5c7.5-4.7 12.7-12.4 14.2-21.1 1.3-7.3.2-14.8-3.3-21.3 2.4-3.6 4-7.6 4.7-11.8 1.7-9-.4-18.2-5.7-25.5"/>
<path fill="#fff" d="M40.9 103.9a21.8 21.8 0 01-23.4-8.7c-3.2-4.4-4.4-9.9-3.5-15.3l.6-2.6.5-1.6 1.4 1c3.3 2.4 6.9 4.2 10.8 5.4l1 .3-.1 1c-.1 1.4.3 2.9 1.1 4.1a6.62 6.62 0 008.8 2L65.5 72c1.4-.9 2.3-2.2 2.6-3.8.3-1.6-.1-3.3-1-4.6a6.56 6.56 0 00-8.8-1.9l-10.5 6.7a18.6 18.6 0 01-5.6 2.4 21.8 21.8 0 01-23.4-8.7 20.2 20.2 0 01-3.4-15.3c.9-5.2 4.1-9.9 8.6-12.7l27.5-17.5c1.7-1.1 3.6-1.9 5.6-2.5a21.8 21.8 0 0123.4 8.7c3.2 4.4 4.4 9.9 3.5 15.3-.2.9-.4 1.7-.7 2.6l-.5 1.6-1.4-1c-3.3-2.4-6.9-4.2-10.8-5.4l-1-.3.1-1c.1-1.4-.3-2.9-1.1-4.1a6.56 6.56 0 00-8.8-1.9L32.4 46.1c-1.4.9-2.3 2.2-2.6 3.8s.1 3.3 1 4.6a6.56 6.56 0 008.8 1.9l10.5-6.7c1.7-1.1 3.6-1.9 5.6-2.5a21.8 21.8 0 0123.4 8.7c3.2 4.4 4.4 9.9 3.5 15.3-.9 5.2-4.1 9.9-8.6 12.7l-27.5 17.5c-1.7 1.1-3.6 1.9-5.6 2.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -41,6 +41,7 @@
import Settings from "./components/settings/Settings.svelte";
import Transition from "./components/base/Transition.svelte";
import Orgs from "./components/orgs/Orgs.svelte";
import CardAssignment from "./components/general/CardAssignment.svelte";
import Runners from "./components/runners/Runners.svelte";
import Footer from "./components/general/Footer.svelte";
import TracksOverview from "./components/tracks/TracksOverview.svelte";
@@ -135,12 +136,17 @@
</Route>
<Route path="/runners/*">
<Route path="/">
<Runners />
<Runners created_via="all" />
</Route>
<Route path="/:runnerid" let:params>
<RunnerDetail {params} />
</Route>
</Route>
<Route path="/cardassignment/*">
<Route path="/">
<CardAssignment />
</Route>
</Route>
<Route path="/teams/*">
<Route path="/">
<Teams />

View File

@@ -20,7 +20,6 @@
OpenAPI.TOKEN = value.access_token;
const jwtinfo = JSON.parse(atob(OpenAPI.TOKEN.split(".")[1]));
store.login(value, jwtinfo);
toast($_("welcome_wavinghand"));
}
}
});
@@ -50,7 +49,6 @@
store.login(result.access_token, jwtinfo);
location.replace("/");
toast.dismiss();
toast($_("welcome_wavinghand"));
})
.catch((err) => {
toast.dismiss();

View File

@@ -5,8 +5,11 @@
import { RunnerCardService } from "@odit/lfk-client-js";
import { createEventDispatcher } from "svelte";
import toast from "svelte-french-toast";
import DocumentServer from "../pdf_generation/DocumentServer";
export let bulk_modal_open;
const dispatch = createEventDispatcher();
const documentServer = new DocumentServer(config.baseurl_documentserver,config.documentserver_key);
$: card_count = 0;
$: is_card_count_valid = card_count > 0;
@@ -60,24 +63,7 @@
toast.success($_("created-blanco-cards"));
toast.loading($_("generating-pdf"));
dispatch("created", { cards: result });
fetch(
`${config.baseurl_documentserver}/cards?&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(result),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
documentServer.generateCards(result, "de")
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
@@ -105,14 +91,14 @@
{#if bulk_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
bulk_modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -130,10 +116,10 @@
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w- rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -149,18 +135,18 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:mt-0">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-bulk-blanco-cards")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"just-enter-how-many-you-want-and-the-system-will-create-them"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="amount"
@@ -177,7 +163,7 @@
type="number"
step="1"
name="amount"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
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-neutral-800 p-2"
placeholder="400"
/>
<span
@@ -197,13 +183,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit_with_print}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create-and-generate-pdf")}
</button>
@@ -212,7 +198,7 @@
class:opacity-50={!createbtnenabled}
on:click={submit_without_print}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-400 text-base font-medium text-white hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create-without-pdf")}
</button>
@@ -221,7 +207,7 @@
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"
class="mr-auto 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"
>
{$_("cancel")}
</button>

View File

@@ -84,14 +84,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -104,15 +104,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -128,11 +128,11 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:mt-0">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-card")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_("you-can-provide-a-runner-but-you-dont-have-to")}
{$_(
@@ -140,7 +140,7 @@
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="donor"
@@ -148,7 +148,7 @@
>{$_("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"
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-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)}
items={runners}
@@ -165,13 +165,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create")}
</button>
@@ -180,7 +180,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -78,14 +78,14 @@
{#if edit_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
edit_modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -98,15 +98,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -122,16 +122,16 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("edit-a-card")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_("you-can-provide-a-runner-but-you-dont-have-to")}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="runner"
@@ -139,7 +139,7 @@
>{$_("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"
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-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)}
items={runners}
@@ -174,13 +174,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("save-changes")}
</button>
@@ -189,7 +189,7 @@
edit_modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -5,12 +5,12 @@
{#if enabled}
<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 border border-current bg-green-100 text-green-800"
>{$_("enabled")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-red-100 text-red-800"
>{$_("disabled")}</span
>
{/if}

View File

@@ -11,29 +11,29 @@
</script>
<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">
{$_("cards")}
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("add-card")}
</button>
<button
on:click={() => {
bulk_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("create-bulk-cards")}
</button>
{/if}
</span>
</h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>
{$_("add-card")}
</button>
<button
on:click={() => {
bulk_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>
{$_("create-bulk-cards")}
</button>
{/if}
<CardsOverview bind:current_cards bind:addCards />
</section>

View File

@@ -5,7 +5,7 @@
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="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>{$_("add-your-first-card")}</span>
</p>

View File

@@ -151,7 +151,6 @@
}
onMount(async () => {
toast.loading($_("loading-cards"));
let page = 0;
let pagesize = 500;
while (page >= 0) {
@@ -172,8 +171,6 @@
dataLoaded = true;
page++;
}
toast.dismiss();
toast.success($_("all-cards-loaded"));
});
</script>
@@ -219,7 +216,7 @@
{#if selected.length > 0}
<button
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
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"
id="options-menu"
on:click={async () => {
const prom = [];

View File

@@ -34,14 +34,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -54,15 +54,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -78,15 +78,10 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("confirm-delete")}
{$_("please-confirm-the-deletion-of-card")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_("please-confirm-the-deletion-of-card")}
</p>
</div>
<div class="w-full">
{$_("card")} #{delete_card.code}<br />
<span class="inline-block">
@@ -104,11 +99,11 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("delete")}
</button>
@@ -117,7 +112,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -43,7 +43,7 @@
$: address_zipcode_value = "";
$: address_city_value = "";
$: processed_last_submit = true;
$: address_checked = true;
$: address_checked = false;
$: isPhoneValidOrEmpty =
(phone_input_value.includes("+") &&
isMobilePhone(
@@ -136,14 +136,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -156,15 +156,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -179,18 +179,18 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-contact")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-required-information-to-add-a-new-contact"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="firstname"
@@ -208,7 +208,7 @@
bind:this={firstname_input}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isFirstnameValid}
<span
@@ -231,7 +231,7 @@
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
@@ -250,7 +250,7 @@
bind:this={lastname_input}
type="text"
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isLastnameValid}
<span
@@ -270,7 +270,7 @@
name="team"
multiple
bind:value={selected_team}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
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-neutral-800 rounded-md p-2"
>
{#each teams as team}
<option value={team.id}>
@@ -300,7 +300,7 @@
bind:this={phone_input}
type="tel"
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isPhoneValidOrEmpty}
<span
@@ -328,7 +328,7 @@
bind:this={email_input}
type="email"
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isEmailValidOrEmpty}
<span
@@ -349,7 +349,7 @@
/>
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-medium text-gray-700"
<label for="comments" class="font-semibold text-gray-700"
>{$_("address")}</label
>
</div>
@@ -371,7 +371,7 @@
bind:this={address_input1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isAddress1Valid}
<span
@@ -394,7 +394,7 @@
bind:this={address_input2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
@@ -413,7 +413,7 @@
bind:this={address_zipcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !iszipcodevalid}
<span
@@ -439,7 +439,7 @@
bind:this={address_city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !iscityvalid}
<span
@@ -454,13 +454,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create")}
</button>
@@ -469,7 +469,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -1,417 +1,399 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import {
GroupContactService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail";
import toast from "svelte-french-toast";
let data_loaded = false;
let orgs = [];
let teams = [];
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: changes_performed = !(
JSON.stringify(original_data) === JSON.stringify(editable)
);
$: isEmailValid =
(editable.email || "") === "" ||
(editable.email && isEmail(editable.email || ""));
$: isFirstnameValid = editable.firstname !== "";
$: isLastnameValid = editable.lastname !== "";
$: save_enabled =
changes_performed &&
isFirstnameValid &&
isLastnameValid &&
isEmailValid &&
isPhoneValidOrEmpty &&
((isAddress1Valid && iszipcodevalid && iscityvalid) ||
editable.address_checked === false);
const promise = GroupContactService.groupContactControllerGetOne(
params.contact
).then((data) => {
data_loaded = true;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
editable.groups = editable.groups.map((g) => g.id);
original_data.groups = original_data.groups.map((g) => g.id);
editable.address_checked = editable.address.address1 !== null;
original_data.address_checked = editable.address.address1 !== null;
if (editable.address_checked === false) {
editable.address = {
address1: "",
address2: "",
city: "",
postalcode: "",
country: "",
};
}
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
$: isPhoneValidOrEmpty =
editable.phone?.includes("+") ||
editable.phone === "" ||
editable.phone === null;
$: isAddress1Valid = editable.address?.address1?.trim().length !== 0;
$: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0;
$: iscityvalid = editable.address?.city?.trim().length !== 0;
function submit() {
if (data_loaded === true && save_enabled) {
toast.loading($_("contact-is-being-updated"));
editable.address.country = "DE";
if (editable.address_checked === false) {
editable.address = null;
}
if (editable.email) editable.email = editable.email;
if (editable.phone) editable.phone = editable.phone;
if (editable.middlename) editable.middlename = editable.middlename;
GroupContactService.groupContactControllerPut(original_data.id, editable)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.dismiss();
toast.success($_("updated-contact"));
})
.catch((err) => {});
} else {
}
}
function deleteContact() {
GroupContactService.groupContactControllerRemove(original_data.id, true)
.then((resp) => {
location.replace("./");
})
.catch((err) => {});
}
import { _ } from "svelte-i18n";
import store from "../../store";
import {
GroupContactService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail";
import toast from "svelte-french-toast";
let data_loaded = false;
let orgs = [];
let teams = [];
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: changes_performed = !(
JSON.stringify(original_data) === JSON.stringify(editable)
);
$: isEmailValid =
(editable.email || "") === "" ||
(editable.email && isEmail(editable.email || ""));
$: isFirstnameValid = editable.firstname !== "";
$: isLastnameValid = editable.lastname !== "";
$: save_enabled =
changes_performed &&
isFirstnameValid &&
isLastnameValid &&
isEmailValid &&
isPhoneValidOrEmpty &&
((isAddress1Valid && iszipcodevalid && iscityvalid) ||
editable.address_checked === false);
const promise = GroupContactService.groupContactControllerGetOne(
params.contact
).then((data) => {
data_loaded = true;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
editable.groups = editable.groups.map((g) => g.id);
original_data.groups = original_data.groups.map((g) => g.id);
editable.address_checked = editable.address.address1 !== null;
original_data.address_checked = editable.address.address1 !== null;
if (editable.address_checked === false) {
editable.address = {
address1: "",
address2: "",
city: "",
postalcode: "",
country: "",
};
}
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
$: isPhoneValidOrEmpty =
editable.phone?.includes("+") ||
editable.phone === "" ||
editable.phone === null;
$: isAddress1Valid = editable.address?.address1?.trim().length !== 0;
$: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0;
$: iscityvalid = editable.address?.city?.trim().length !== 0;
function submit() {
if (data_loaded === true && save_enabled) {
toast.loading($_("contact-is-being-updated"));
editable.address.country = "DE";
if (editable.address_checked === false) {
editable.address = null;
}
if (editable.email) editable.email = editable.email;
if (editable.phone) editable.phone = editable.phone;
if (editable.middlename) editable.middlename = editable.middlename;
GroupContactService.groupContactControllerPut(original_data.id, editable)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.dismiss();
toast.success($_("updated-contact"));
})
.catch((err) => {});
} else {
}
}
function deleteContact() {
GroupContactService.groupContactControllerRemove(original_data.id, true)
.then((resp) => {
location.replace("./");
})
.catch((err) => {});
}
</script>
{#await promise}
{$_("loading-contact-details")}
{$_("loading-contact-details")}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z"
/></svg
>
</li>
<li class="flex items-center ml-2">
<a class="mr-2" href="./">{$_("contacts")}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
</li>
<li class="flex items-center">
<span class="mr-2"
>{original_data.firstname}
{original_data.middlename || ""}
{original_data.lastname}</span
>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.firstname}
{original_data.middlename || ""}
{original_data.lastname}
<span data-id="contact_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:DELETE")}
{#if delete_triggered}
<button
on:click={deleteContact}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("delete-contact")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("save-changes")}</button
>
{/if}
</span>
</div>
<!-- -->
<div class="text-sm w-full">
<label for="firstname" class="font-medium text-gray-700"
>{$_("first-name")}</label
>
<input
autocomplete="off"
placeholder={$_("first-name")}
type="text"
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("first-name-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label for="middlename" class="font-medium text-gray-700"
>{$_("middle-name")}</label
>
<input
autocomplete="off"
placeholder={$_("middle-name")}
type="text"
bind:value={editable.middlename}
name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div>
<div class="text-sm w-full">
<label for="lastname" class="font-medium text-gray-700"
>{$_("last-name")}</label
>
<input
autocomplete="off"
placeholder={$_("last-name")}
type="text"
bind:value={editable.lastname}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("last-name-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label for="email" class="font-medium text-gray-700"
>{$_("e-mail-adress")}</label
>
<input
autocomplete="off"
placeholder={$_("e-mail-adress")}
type="email"
bind:value={editable.email}
class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid}
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-email-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label for="phone" class="font-medium text-gray-700">{$_("phone")}</label>
<input
autocomplete="off"
placeholder={$_("phone")}
type="tel"
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
bind:value={editable.phone}
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-international-phone-number-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_("groups")}</span>
<select
bind:value={editable.groups}
name="team"
multiple
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
>
{#each teams as team}
<option value={team.id}>
{team.parentGroup.name}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value={org.id}>{org.name}</option>
{/each}
</select>
</div>
<!-- -->
<div class="flex items-start mt-2">
<div class="flex items-center h-5">
<input
bind:checked={editable.address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-medium text-gray-700"
>{$_("address")}</label
>
</div>
</div>
{#if editable.address_checked === true}
<div class="col-span-6">
<label for="address1" class="block text-sm font-medium text-gray-700"
>{$_("address")}</label
>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("address-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label for="address2" class="block text-sm font-medium text-gray-700"
>{$_("apartment-suite-etc")}</label
>
<input
autocomplete="off"
placeholder={$_("apartment-suite-etc")}
bind:value={editable.address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label for="zipcode" class="block text-sm font-medium text-gray-700"
>{$_("zip-postal-code")}</label
>
<input
autocomplete="off"
placeholder={$_("zip-postal-code")}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-zipcode-postal-code-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label for="city" class="block text-sm font-medium text-gray-700"
>{$_("city")}</label
>
<input
autocomplete="off"
placeholder={$_("city")}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-city-is-required")}
</span>
{/if}
</div>
{/if}
</section>
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<a class="mr-2" href="./"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="inline-block"
><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg
>
{$_("contacts")}</a
>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-4 text-3xl font-extrabold leading-tight">
{original_data.firstname}
{original_data.middlename || ""}
{original_data.lastname}
<div data-id="contact_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:DELETE")}
{#if delete_triggered}
<button
on:click={deleteContact}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
>{$_("delete-contact")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>{$_("save-changes")}</button
>
{/if}
</div>
</div>
<!-- -->
<div class="text-sm w-full mt-2">
<label for="firstname" class="font-semibold text-gray-700"
>{$_("first-name")}</label
>
<input
autocomplete="off"
placeholder={$_("first-name")}
type="text"
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("first-name-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full mt-2">
<label for="middlename" class="font-semibold text-gray-700"
>{$_("middle-name")}</label
>
<input
autocomplete="off"
placeholder={$_("middle-name")}
type="text"
bind:value={editable.middlename}
name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="text-sm w-full mt-2">
<label for="lastname" class="font-semibold text-gray-700"
>{$_("last-name")}</label
>
<input
autocomplete="off"
placeholder={$_("last-name")}
type="text"
bind:value={editable.lastname}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("last-name-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full mt-2">
<label for="email" class="font-semibold text-gray-700"
>{$_("e-mail-adress")}</label
>
<input
autocomplete="off"
placeholder={$_("e-mail-adress")}
type="email"
bind:value={editable.email}
class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid}
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-email-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full mt-2">
<label for="phone" class="font-semibold text-gray-700">{$_("phone")}</label>
<input
autocomplete="off"
placeholder={$_("phone")}
type="tel"
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
bind:value={editable.phone}
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-international-phone-number-is-required")}
</span>
{/if}
</div>
<div class="text-sm w-full mt-2">
<span class="font-semibold text-gray-700">{$_("groups")}</span>
<select
bind:value={editable.groups}
name="team"
multiple
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
>
{#each teams as team}
<option value={team.id}>
{team.parentGroup.name}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value={org.id}>{org.name}</option>
{/each}
</select>
</div>
<!-- -->
<div class="flex items-start mt-2">
<div class="flex items-center h-5">
<input
bind:checked={editable.address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-semibold text-gray-700"
>{$_("address")}</label
>
</div>
</div>
{#if editable.address_checked === true}
<div class="col-span-6">
<label for="address1" class="block text-sm font-medium text-gray-700"
>{$_("address")}</label
>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("address-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label for="address2" class="block text-sm font-medium text-gray-700"
>{$_("apartment-suite-etc")}</label
>
<input
autocomplete="off"
placeholder={$_("apartment-suite-etc")}
bind:value={editable.address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label for="zipcode" class="block text-sm font-medium text-gray-700"
>{$_("zip-postal-code")}</label
>
<input
autocomplete="off"
placeholder={$_("zip-postal-code")}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-zipcode-postal-code-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label for="city" class="block text-sm font-medium text-gray-700"
>{$_("city")}</label
>
<input
autocomplete="off"
placeholder={$_("city")}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-city-is-required")}
</span>
{/if}
</div>
{/if}
</section>
{:catch error}
<PromiseError {error} />
<PromiseError {error} />
{/await}

View File

@@ -8,20 +8,20 @@
</script>
<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">
{$_("contacts")}
{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("create-a-new-contact")}
</button>
{/if}
</span>
</h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm"
>
{$_("create-a-new-contact")}
</button>
{/if}
<ContactsOverview bind:current_contacts />
</section>

View File

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

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

View File

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

View File

@@ -7,13 +7,13 @@
</script>
<a {href}>
<div class="p-4 rounded-lg bg-white border border-grey-100">
<div class="p-3 py-4 sm:p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-normal text-grey-500">
<div class="text-md sm:text-xs uppercase font-normal text-grey-500">
{title}
</div>
<div class="text-xl font-bold font-mono">{value}</div>
<div class="text-2xl sm:text-xl font-bold font-mono">{value}</div>
</div>
<slot />
</div>

View File

@@ -100,7 +100,6 @@
}
onMount(async () => {
toast.loading($_("loading-donors"));
donors = (await DonorService.donorControllerGetAll()).map(
(r) => {
return { label: getDonorLabel(r), value: r };
@@ -111,21 +110,19 @@
return { label: getDonorLabel(r), value: r };
}
);
toast.dismiss();
toast.success($_("all-donors-loaded"));
});
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -138,15 +135,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -161,14 +158,14 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{#if is_fixed}
{$_("create-a-new-fixed-donation")}
{:else}{$_("create-a-new-distance-donation")}{/if}
</h3>
<label class="content-center align-middle object-center">
<span class="ml-2 text-base" class:text-gray-300={is_fixed}
<span class="text-base" class:text-gray-300={is_fixed}
>{$_("distance-donation")}</span
>
<input
@@ -180,14 +177,14 @@
>{$_("fixed-donation")}</span
>
</label>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-nessecary-information-to-create-a-new-donation"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="donor"
@@ -195,7 +192,7 @@
>{$_("donor")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
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-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={donors}
@@ -215,7 +212,7 @@
>{$_("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"
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-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={runners}
@@ -247,7 +244,7 @@
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
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-neutral-800 p-2"
placeholder="2.00"
/>
<span
@@ -292,13 +289,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create")}
</button>
@@ -307,7 +304,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -76,14 +76,14 @@
{#if payment_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
payment_modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -96,15 +96,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -120,18 +120,18 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("enter-payment")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"you-can-enter-the-donations-paid-amount-manually-or-use-the-max-button-to-use-the-donations-exact-amount"
)}
</p>
</div>
<div class="grid grid-cols gap-6">
<div class="grid grid-cols gap-2 lg:gap-6">
<div class="w-full">
<label
for="token"
@@ -179,13 +179,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("save-changes")}
</button>
@@ -194,7 +194,7 @@
payment_modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -37,14 +37,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -57,15 +57,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -80,15 +80,10 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("confirm-delete")}
{$_("please-confirm-the-deletion-of-donation")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_("please-confirm-the-deletion-of-donation")}
</p>
</div>
<div class="w-full">
<span class="inline-block"
><b>{$_("donor")}</b>: {delete_donation.donor.firstname}
@@ -98,11 +93,11 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("delete")}
</button>
@@ -111,7 +106,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -1,364 +1,352 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import {
DonationService,
DonorService,
RunnerService,
} from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import store from "../../store";
import {
DonationService,
DonorService,
RunnerService,
} from "@odit/lfk-client-js";
import toast from "svelte-french-toast";
import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select";
let data_loaded = false;
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: donor = {};
$: runner = {};
$: current_donors = [];
$: current_runners = [];
$: amount_input = 0;
$: is_amount_valid = amount_input > 0;
$: paid_amount_input = 0;
$: is_paid_amount_valid = paid_amount_input > 0;
$: is_everything_set =
editable.donor != null &&
((original_data.responseType == "DISTANCEDONATION" &&
editable?.runner != null) ||
original_data.responseType !== "DISTANCEDONATION");
$: changes_performed =
!(JSON.stringify(original_data) === JSON.stringify(editable)) ||
(original_data.responseType == "DISTANCEDONATION" &&
!(Math.floor(amount_input * 100) === original_data.amountPerDistance)) ||
(original_data.responseType !== "DISTANCEDONATION" &&
!(Math.floor(amount_input * 100) === original_data.amount)) ||
!(Math.floor(paid_amount_input * 100) === original_data.paidAmount);
$: save_enabled = changes_performed && is_amount_valid && is_everything_set;
import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select";
let data_loaded = false;
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: donor = {};
$: runner = {};
$: current_donors = [];
$: current_runners = [];
$: amount_input = 0;
$: is_amount_valid = amount_input > 0;
$: paid_amount_input = 0;
$: is_paid_amount_valid = paid_amount_input > 0;
$: is_everything_set =
editable.donor != null &&
((original_data.responseType == "DISTANCEDONATION" &&
editable?.runner != null) ||
original_data.responseType !== "DISTANCEDONATION");
$: changes_performed =
!(JSON.stringify(original_data) === JSON.stringify(editable)) ||
(original_data.responseType == "DISTANCEDONATION" &&
!(Math.floor(amount_input * 100) === original_data.amountPerDistance)) ||
(original_data.responseType !== "DISTANCEDONATION" &&
!(Math.floor(amount_input * 100) === original_data.amount)) ||
!(Math.floor(paid_amount_input * 100) === original_data.paidAmount);
$: save_enabled = changes_performed && is_amount_valid && is_everything_set;
const promise = DonationService.donationControllerGetOne(
params.donationid
).then((data) => {
data_loaded = true;
original_data = Object.assign({}, data);
editable = Object.assign({}, original_data);
paid_amount_input = data.paidAmount / 100;
if (data.responseType == "DISTANCEDONATION") {
amount_input = data.amountPerDistance / 100;
RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val.map((r) => {
return { label: getDonorLabel(r), value: r };
});
runner = current_runners.find((g) => g.value.id == editable.runner.id);
});
} else {
amount_input = data.amount / 100;
}
DonorService.donorControllerGetAll().then((val) => {
current_donors = val.map((r) => {
return { label: getDonorLabel(r), value: r };
});
donor = current_donors.find((g) => g.value.id == editable.donor.id);
});
});
const getDonorLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterDonors = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase());
const promise = DonationService.donationControllerGetOne(
params.donationid
).then((data) => {
data_loaded = true;
original_data = Object.assign({}, data);
editable = Object.assign({}, original_data);
paid_amount_input = data.paidAmount / 100;
if (data.responseType == "DISTANCEDONATION") {
amount_input = data.amountPerDistance / 100;
RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val.map((r) => {
return { label: getDonorLabel(r), value: r };
});
runner = current_runners.find((g) => g.value.id == editable.runner.id);
});
} else {
amount_input = data.amount / 100;
}
DonorService.donorControllerGetAll().then((val) => {
current_donors = val.map((r) => {
return { label: getDonorLabel(r), value: r };
});
donor = current_donors.find((g) => g.value.id == editable.donor.id);
});
});
const getDonorLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterDonors = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase());
function submit() {
if (data_loaded === true && save_enabled) {
toast($_("updating-donation"));
let postdata = {};
editable.paidAmount = paid_amount_input * 100;
if (original_data.responseType === "DISTANCEDONATION") {
editable.amountPerDistance = Math.floor(amount_input * 100);
postdata = Object.assign(postdata, editable);
postdata.runner = postdata.runner.id;
postdata.donor = postdata.donor.id;
DonationService.donationControllerPutDistance(
original_data.id,
postdata
)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("donation-updated"));
})
.catch((err) => {});
} else {
editable.amount = Math.floor(amount_input * 100);
postdata = Object.assign(postdata, editable);
postdata.donor = postdata.donor.id;
DonationService.donationControllerPutFixed(original_data.id, postdata)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("donation-updated"));
})
.catch((err) => {});
}
} else {
}
}
function deleteDonation() {
DonationService.donationControllerRemove(original_data.id, false)
.then((resp) => {
toast($_("donation-deleted"));
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_donor = original_data;
});
}
function submit() {
if (data_loaded === true && save_enabled) {
toast($_("updating-donation"));
let postdata = {};
editable.paidAmount = paid_amount_input * 100;
if (original_data.responseType === "DISTANCEDONATION") {
editable.amountPerDistance = Math.floor(amount_input * 100);
postdata = Object.assign(postdata, editable);
postdata.runner = postdata.runner.id;
postdata.donor = postdata.donor.id;
DonationService.donationControllerPutDistance(
original_data.id,
postdata
)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("donation-updated"));
})
.catch((err) => {});
} else {
editable.amount = Math.floor(amount_input * 100);
postdata = Object.assign(postdata, editable);
postdata.donor = postdata.donor.id;
DonationService.donationControllerPutFixed(original_data.id, postdata)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("donation-updated"));
})
.catch((err) => {});
}
} else {
}
}
function deleteDonation() {
DonationService.donationControllerRemove(original_data.id, false)
.then((resp) => {
toast.success($_("donation-deleted"));
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_donor = original_data;
});
}
</script>
{#await promise}
{$_("loading-donation-details")}
{$_("loading-donation-details")}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</li>
<li class="flex items-center ml-2">
<a class="mr-2" href="./">{$_("donations")}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
</li>
<li class="flex items-center">
<span class="mr-2">{original_data.id}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.donor.firstname}
{original_data.donor.middlename || ""}
{original_data.donor.lastname}
&gt;
{#if original_data.responseType == "DISTANCEDONATION"}
{original_data.runner.firstname}
{original_data.runner.middlename || ""}
{original_data.runner.lastname}
{:else}
{$_("fixed-donation")}:
{amount_input.toFixed(2).toLocaleString("de-DE", { valute: "EUR" })}
{/if}
<span data-id="donation_actions_${original_data.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:DELETE")}
{#if delete_triggered}
<button
on:click={deleteDonation}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
>{$_("delete-donation")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:"
>{$_("save-changes")}</button
>
{/if}
</span>
</div>
<!-- -->
<div>
<span class="font-medium text-gray-700"
>{$_("total-donation-amount")}:</span
>
<span
>{(editable.amount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</span
>
|
<span class="font-medium text-gray-700">{$_("paid-amount")}:</span>
<span
>{(editable.paidAmount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</span
>
|
<span class="font-medium text-gray-700">{$_("status")}:</span>
{#if editable.status == "PAID"}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("paid")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("open")}</span
>
{/if}
</div>
<br />
<div class=" w-full">
<label for="donor" class="block font-medium text-gray-700"
>{$_("donor")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={current_donors}
showChevron={true}
placeholder={$_("search-for-donor-name-or-id")}
noOptionsMessage={$_("no-donors-found")}
bind:selectedValue={donor}
on:select={(selectedValue) => {
editable.donor = selectedValue.detail.value;
editable.donor.donationAmount = original_data.donor.donationAmount;
editable.donor.paidDonationAmount =
original_data.donor.paidDonationAmount;
}}
on:clear={() => (editable.donor = null)}
/>
</div>
{#if original_data.responseType == "DISTANCEDONATION"}
<div class=" w-full">
<label for="donor" class="block font-medium text-gray-700"
>{$_("runner")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={current_runners}
showChevron={true}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
bind:selectedValue={runner}
on:select={(selectedValue) =>
(editable.runner = selectedValue.detail.value)}
on:clear={() => (editable.runner = null)}
/>
</div>
{/if}
<div class=" w-full">
<label for="lastname" class="font-medium text-gray-700">
{#if original_data.responseType == "DISTANCEDONATION"}
{$_("amount-per-kilometer")}
{:else}{$_("donation-amount")}{/if}
</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="2.00"
/>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500"
></span
>
</div>
{#if !is_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("donation-amount-must-be-greater-that-0-00eur")}
</span>
{/if}
</div>
<div class="w-full">
<label for="token" class="block text-sm font-medium text-gray-700"
>{$_("paid-amount")}</label
>
<div
class="inline-flex border-gray-300 border rounded-l-md rounded-r-md bg-gray-50 text-gray-500 w-full"
>
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={paid_amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm p-2"
placeholder="2.00"
/>
<button
on:click={() => {
paid_amount_input = paid_amount_input = (
original_data.amount / 100
).toFixed(2);
}}
class="inline-flex items-center p-r-2 text-indigo-300 hover:text-indigo-700 text-sm"
>MAX</button
>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
></span
>
</div>
{#if !is_paid_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("payment-amount-must-be-greater-than-0-00eur")}
</span>
{/if}
</div>
</section>
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="mt-2 w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<a class="mr-2" href="./"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="inline-block"
><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg
>
{$_("donations")}</a
>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-4 text-3xl font-extrabold leading-tight">
{original_data.donor.firstname}
{original_data.donor.middlename || ""}
{original_data.donor.lastname}
&gt;
{#if original_data.responseType == "DISTANCEDONATION"}
{original_data.runner.firstname}
{original_data.runner.middlename || ""}
{original_data.runner.lastname}
{:else}
{$_("fixed-donation")}:
{amount_input.toFixed(2).toLocaleString("de-DE", { valute: "EUR" })}
{/if}
[#{original_data.id}]
<div data-id="donation_actions_${original_data.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:DELETE")}
{#if delete_triggered}
<button
on:click={deleteDonation}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
>{$_("delete-donation")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>{$_("save-changes")}</button
>
{/if}
</div>
</div>
<!-- -->
<div>
<span class="font-semibold text-gray-700"
>{$_("total-donation-amount")}:</span
>
<span
>{(editable.amount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</span
>
|
<span class="font-semibold text-gray-700">{$_("paid-amount")}:</span>
<span
>{(editable.paidAmount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</span
>
|
<span class="font-semibold text-gray-700">{$_("status")}:</span>
{#if editable.status == "PAID"}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-green-100 text-green-800"
>{$_("paid")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-red-100 text-red-800"
>{$_("open")}</span
>
{/if}
</div>
<br />
<div class=" mt-2 w-full">
<label for="donor" class="block font-semibold text-gray-700"
>{$_("donor")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={current_donors}
showChevron={true}
placeholder={$_("search-for-donor-name-or-id")}
noOptionsMessage={$_("no-donors-found")}
bind:selectedValue={donor}
on:select={(selectedValue) => {
editable.donor = selectedValue.detail.value;
editable.donor.donationAmount = original_data.donor.donationAmount;
editable.donor.paidDonationAmount =
original_data.donor.paidDonationAmount;
}}
on:clear={() => (editable.donor = null)}
/>
</div>
{#if original_data.responseType == "DISTANCEDONATION"}
<div class=" mt-2 w-full">
<label for="donor" class="block font-semibold text-gray-700"
>{$_("runner")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={current_runners}
showChevron={true}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
bind:selectedValue={runner}
on:select={(selectedValue) =>
(editable.runner = selectedValue.detail.value)}
on:clear={() => (editable.runner = null)}
/>
</div>
{/if}
<div class=" mt-2 w-full">
<label for="lastname" class="font-semibold text-gray-700">
{#if original_data.responseType == "DISTANCEDONATION"}
{$_("amount-per-kilometer")}
{:else}{$_("donation-amount")}{/if}
</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm: border-gray-300 border bg-gray-50 text-neutral-800 p-2"
placeholder="2.00"
/>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500"
></span
>
</div>
{#if !is_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("donation-amount-must-be-greater-that-0-00eur")}
</span>
{/if}
</div>
<div class="mt-2 w-full">
<label for="token" class="block font-semibold text-gray-700"
>{$_("paid-amount")}</label
>
<div
class="inline-flex border-gray-300 border rounded-l-md rounded-r-md bg-gray-50 text-gray-500 w-full"
>
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={paid_amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm p-2"
placeholder="2.00"
/>
<button
on:click={() => {
paid_amount_input = paid_amount_input = (
original_data.amount / 100
).toFixed(2);
}}
class="inline-flex items-center p-r-2 text-indigo-300 hover:text-indigo-700 text-sm"
>MAX</button
>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
></span
>
</div>
{#if !is_paid_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("payment-amount-must-be-greater-than-0-00eur")}
</span>
{/if}
</div>
</section>
{:catch error}
<PromiseError {error} />
<PromiseError {error} />
{/await}

View File

@@ -9,7 +9,7 @@
<div class="flex items-center">
<a
href="../donors/{donor.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 border border-current"
>{donor.firstname}
{#if donor.middlename}{donor.middlename}{/if}
{donor.lastname}</a

View File

@@ -9,7 +9,7 @@
<div class="text-sm font-medium text-gray-900">
<a
href="../runners/{runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 border border-current"
>{runner.firstname}
{#if runner.middlename}{runner.middlename}{/if}
{runner.lastname}</a

View File

@@ -5,12 +5,12 @@
{#if status == "PAID"}
<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 border border-current bg-green-100 text-green-800"
>{$_("paid")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-red-100 text-red-800"
>{$_("open")}</span
>
{/if}

View File

@@ -9,20 +9,20 @@
</script>
<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">
{$_("donations")}
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("add-donation")}
</button>
{/if}
</span>
</h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm"
>
{$_("add-donation")}
</button>
{/if}
<DonationsOverview bind:current_donations bind:addDonations />
</section>

View File

@@ -5,7 +5,7 @@
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="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>{$_("add-your-fist-donation")}</span>
</p>

View File

@@ -163,7 +163,7 @@
...options,
data: current_donations,
}));
toast($_("donation-deleted"));
toast.success($_("donation-deleted"));
}
onMount(async () => {
@@ -231,7 +231,7 @@
bind:value={searchvalue}
placeholder={$_("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
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"

View File

@@ -124,14 +124,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -144,15 +144,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -167,18 +167,18 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-donor")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-nessecary-information-to-add-a-new-donor"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="firstname"
@@ -196,7 +196,7 @@
bind:this={firstname_input}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isFirstnameValid}
<span
@@ -219,7 +219,7 @@
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
@@ -238,7 +238,7 @@
bind:this={lastname_input}
type="text"
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isLastnameValid}
<span
@@ -264,7 +264,7 @@
bind:this={phone_input}
type="tel"
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isPhoneValidOrEmpty}
<span
@@ -292,7 +292,7 @@
bind:this={email_input}
type="email"
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isEmailValidOrEmpty}
<span
@@ -313,7 +313,7 @@
/>
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-medium text-gray-700"
<label for="comments" class="font-semibold text-gray-700"
>{$_("receipt-needed")}</label
>
</div>
@@ -335,7 +335,7 @@
bind:this={address_input1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isAddress1Valid}
<span
@@ -358,7 +358,7 @@
bind:this={address_input2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
@@ -377,7 +377,7 @@
bind:this={address_zipcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !iszipcodevalid}
<span
@@ -403,7 +403,7 @@
bind:this={address_city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !iscityvalid}
<span
@@ -418,13 +418,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create")}
</button>
@@ -433,7 +433,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

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

View File

@@ -1,434 +1,413 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import { DonorService, DonationService } from "@odit/lfk-client-js";
import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail";
import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte";
import toast from "svelte-french-toast";
let data_loaded = false;
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: changes_performed = !(
JSON.stringify(original_data) === JSON.stringify(editable)
);
$: isEmailValid =
(editable.email || "") === "" ||
(editable.email && isEmail(editable.email || ""));
$: isFirstnameValid = editable.firstname !== "";
$: isLastnameValid = editable.lastname !== "";
$: save_enabled =
changes_performed &&
isFirstnameValid &&
isLastnameValid &&
isEmailValid &&
isPhoneValidOrEmpty &&
((isAddress1Valid && iszipcodevalid && iscityvalid) ||
editable.address_checked === false);
const promise = DonorService.donorControllerGetOne(params.donorid).then(
(data) => {
data_loaded = true;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
editable.address_checked = editable.address.address1 !== null;
original_data.address_checked = editable.address.address1 !== null;
if (editable.address_checked === false) {
editable.address = {
address1: "",
address2: "",
city: "",
postalcode: "",
country: "",
};
}
}
);
$: isPhoneValidOrEmpty =
editable.phone?.includes("+") ||
editable.phone === "" ||
editable.phone === null;
$: isAddress1Valid = editable.address?.address1?.trim().length !== 0;
$: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0;
$: iscityvalid = editable.address?.city?.trim().length !== 0;
let modal_open = false;
let delete_donor = {};
function submit() {
if (data_loaded === true && save_enabled) {
toast($_("donor-is-being-updated"));
editable.address.country = "DE";
if (editable.address_checked === false) {
editable.address = null;
}
if (editable.email) editable.email = editable.email;
else editable.email = null;
if (editable.phone) editable.phone = editable.phone;
else editable.phone = null;
if (editable.middlename) editable.middlename = editable.middlename;
editable.receiptNeeded = editable.address_checked;
DonorService.donorControllerPut(original_data.id, editable)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("updated-donor"));
})
.catch((err) => {});
} else {
}
}
function deleteDonor() {
DonorService.donorControllerRemove(original_data.id, false)
.then((resp) => {
toast($_("donor-deleted"));
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_donor = original_data;
});
}
import { DonorService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import store from "../../store";
import toast from "svelte-french-toast";
import isEmail from "validator/es/lib/isEmail";
import PromiseError from "../base/PromiseError.svelte";
let data_loaded = false;
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: changes_performed = !(
JSON.stringify(original_data) === JSON.stringify(editable)
);
$: isEmailValid =
(editable.email || "") === "" ||
(editable.email && isEmail(editable.email || ""));
$: isFirstnameValid = editable.firstname !== "";
$: isLastnameValid = editable.lastname !== "";
$: save_enabled =
changes_performed &&
isFirstnameValid &&
isLastnameValid &&
isEmailValid &&
isPhoneValidOrEmpty &&
((isAddress1Valid && iszipcodevalid && iscityvalid) ||
editable.address_checked === false);
const promise = DonorService.donorControllerGetOne(params.donorid).then(
(data) => {
data_loaded = true;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
editable.address_checked = editable.address.address1 !== null;
original_data.address_checked = editable.address.address1 !== null;
if (editable.address_checked === false) {
editable.address = {
address1: "",
address2: "",
city: "",
postalcode: "",
country: "",
};
}
}
);
$: isPhoneValidOrEmpty =
editable.phone?.includes("+") ||
editable.phone === "" ||
editable.phone === null;
$: isAddress1Valid = editable.address?.address1?.trim().length !== 0;
$: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0;
$: iscityvalid = editable.address?.city?.trim().length !== 0;
function submit() {
if (data_loaded === true && save_enabled) {
toast($_("donor-is-being-updated"));
editable.address.country = "DE";
if (editable.address_checked === false) {
editable.address = null;
}
if (editable.email) editable.email = editable.email;
else editable.email = null;
if (editable.phone) editable.phone = editable.phone;
else editable.phone = null;
if (editable.middlename) editable.middlename = editable.middlename;
editable.receiptNeeded = editable.address_checked;
DonorService.donorControllerPut(original_data.id, editable)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("updated-donor"));
})
.catch((err) => {});
} else {
}
}
function deleteDonor() {
DonorService.donorControllerRemove(original_data.id, true)
.then((resp) => {
toast.success($_("donor-deleted"));
location.replace("./");
})
.catch((err) => {
console.log(err);
});
}
</script>
<ConfirmDonorDeletion bind:modal_open bind:delete_donor />
{#await promise}
{$_("loading-donor-details")}
{$_("loading-donor-details")}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"
/></svg
>
</li>
<li class="flex items-center ml-2">
<a class="mr-2" href="./">{$_("donors")}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
</li>
<li class="flex items-center">
<span class="mr-2"
>{original_data.firstname}
{original_data.middlename || ""}
{original_data.lastname}</span
>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.firstname}
{original_data.middlename || ""}
{original_data.lastname}
<span data-id="donor_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:DELETE")}
{#if delete_triggered}
<button
on:click={deleteDonor}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
>{$_("delete-donor")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:"
>{$_("save-changes")}</button
>
{/if}
</span>
</div>
<!-- -->
<div>
<span class="font-medium text-gray-700"
>{$_("total-donation-amount")}:</span
>
<span
>{(editable.donationAmount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</span
>
|
<span class="font-medium text-gray-700">{$_("total-paid-amount")}:</span>
<span
>{(editable.paidDonationAmount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</span
>
<br />
<span class="font-medium text-gray-700">{$_("donations")}:</span>
{#if original_data.donations.length > 0}
{#each original_data.donations as d}
{#if d.responseType === "DISTANCEDONATION"}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1"
>{d.runner.firstname}
{d.runner.middlename || ""}
{d.runner.lastname}</a
>
{:else}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1"
>{$_("fixed-donation")}:
{(d.amount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</a
>
{/if}
{/each}
{:else}{$_("donor-has-no-associated-donations")}{/if}
</div>
<div class=" w-full">
<label for="firstname" class="font-medium text-gray-700"
>{$_("first-name")}</label
>
<input
autocomplete="off"
placeholder={$_("first-name")}
type="text"
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("first-name-is-required")}
</span>
{/if}
</div>
<div class=" w-full">
<label for="middlename" class="font-medium text-gray-700"
>{$_("middle-name")}</label
>
<input
autocomplete="off"
placeholder={$_("middle-name")}
type="text"
bind:value={editable.middlename}
name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div>
<div class=" w-full">
<label for="lastname" class="font-medium text-gray-700"
>{$_("last-name")}</label
>
<input
autocomplete="off"
placeholder={$_("last-name")}
type="text"
bind:value={editable.lastname}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("last-name-is-required")}
</span>
{/if}
</div>
<div class=" w-full">
<label for="email" class="font-medium text-gray-700"
>{$_("e-mail-adress")}</label
>
<input
autocomplete="off"
placeholder={$_("e-mail-adress")}
type="email"
bind:value={editable.email}
class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid}
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-email-is-required")}
</span>
{/if}
</div>
<div class=" w-full">
<label for="phone" class="font-medium text-gray-700">{$_("phone")}</label>
<input
autocomplete="off"
placeholder={$_("phone")}
type="tel"
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
bind:value={editable.phone}
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-international-phone-number-is-required")}
</span>
{/if}
</div>
<div class="flex items-start mt-2">
<div class="flex items-center h-5">
<input
bind:checked={editable.address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div>
<div class="ml-3">
<label for="comments" class="font-medium text-gray-700"
>{$_("receipt-needed")}</label
>
</div>
</div>
{#if editable.address_checked === true}
<div class="col-span-6">
<label for="address1" class="block font-medium text-gray-700"
>{$_("address")}</label
>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("address-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label for="address2" class="block font-medium text-gray-700"
>{$_("apartment-suite-etc")}</label
>
<input
autocomplete="off"
placeholder={$_("apartment-suite-etc")}
bind:value={editable.address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label for="zipcode" class="block font-medium text-gray-700"
>{$_("zip-postal-code")}</label
>
<input
autocomplete="off"
placeholder={$_("zip-postal-code")}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-zipcode-postal-code-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label for="city" class="block font-medium text-gray-700"
>{$_("city")}</label
>
<input
autocomplete="off"
placeholder={$_("city")}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm: border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-city-is-required")}
</span>
{/if}
</div>
{/if}
</section>
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<a class="mr-2" href="./"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="inline-block"
><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg
>
{$_("donors")}</a
>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-4 text-3xl font-extrabold leading-tight">
{original_data.firstname}
{original_data.middlename || ""}
{original_data.lastname}
<div data-id="donor_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:DELETE")}
{#if delete_triggered}
<button
on:click={deleteDonor}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
>{$_("delete-donor")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>{$_("save-changes")}</button
>
{/if}
</div>
</div>
<!-- -->
<div>
<span class="font-semibold text-gray-700"
>{$_("total-donation-amount")}:</span
>
<span
>{(editable.donationAmount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</span
>
|
<span class="font-semibold text-gray-700">{$_("total-paid-amount")}:</span
>
<span
>{(editable.paidDonationAmount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</span
>
<br />
<span class="font-semibold text-gray-700">{$_("donations")}:</span>
{#if original_data.donations.length > 0}
{#each original_data.donations as d}
{#if d.responseType === "DISTANCEDONATION"}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1"
>{d.runner.firstname}
{d.runner.middlename || ""}
{d.runner.lastname}</a
>
{:else}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-green-700 text-white mr-1"
>{$_("fixed-donation")}:
{(d.amount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</a
>
{/if}
{/each}
{:else}{$_("donor-has-no-associated-donations")}{/if}
</div>
<div class="mt-2 w-full">
<label for="firstname" class="font-semibold text-gray-700"
>{$_("first-name")}</label
>
<input
autocomplete="off"
placeholder={$_("first-name")}
type="text"
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname}
name="firstname"
class="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-neutral-800 rounded-md p-2"
/>
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("first-name-is-required")}
</span>
{/if}
</div>
<div class="mt-2 w-full">
<label for="middlename" class="font-semibold text-gray-700"
>{$_("middle-name")}</label
>
<input
autocomplete="off"
placeholder={$_("middle-name")}
type="text"
bind:value={editable.middlename}
name="middlename"
class="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-neutral-800 rounded-md p-2"
/>
</div>
<div class="mt-2 w-full">
<label for="lastname" class="font-semibold text-gray-700"
>{$_("last-name")}</label
>
<input
autocomplete="off"
placeholder={$_("last-name")}
type="text"
bind:value={editable.lastname}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
name="lastname"
class="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-neutral-800 rounded-md p-2"
/>
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("last-name-is-required")}
</span>
{/if}
</div>
<div class="mt-2 w-full">
<label for="email" class="font-semibold text-gray-700"
>{$_("e-mail-adress")}</label
>
<input
autocomplete="off"
placeholder={$_("e-mail-adress")}
type="email"
bind:value={editable.email}
class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid}
name="email"
class="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-neutral-800 rounded-md p-2"
/>
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-email-is-required")}
</span>
{/if}
</div>
<div class="mt-2 w-full">
<label for="phone" class="font-semibold text-gray-700"
>{$_("phone")}</label
>
<input
autocomplete="off"
placeholder={$_("phone")}
type="tel"
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
bind:value={editable.phone}
name="phone"
class="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-neutral-800 rounded-md p-2"
/>
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-international-phone-number-is-required")}
</span>
{/if}
</div>
<div class="flex items-start mt-2">
<div class="flex items-center h-5">
<input
bind:checked={editable.address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div>
<div class="ml-3">
<label for="comments" class="font-semibold text-gray-700"
>{$_("receipt-needed")}</label
>
</div>
</div>
{#if editable.address_checked === true}
<div class="col-span-6">
<label for="address1" class="block font-medium text-gray-700"
>{$_("address")}</label
>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="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-neutral-800 rounded-md p-2"
/>
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("address-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label for="address2" class="block font-medium text-gray-700"
>{$_("apartment-suite-etc")}</label
>
<input
autocomplete="off"
placeholder={$_("apartment-suite-etc")}
bind:value={editable.address.address2}
type="text"
name="address2"
class="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-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label for="zipcode" class="block font-medium text-gray-700"
>{$_("zip-postal-code")}</label
>
<input
autocomplete="off"
placeholder={$_("zip-postal-code")}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="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-neutral-800 rounded-md p-2"
/>
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-zipcode-postal-code-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label for="city" class="block font-medium text-gray-700"
>{$_("city")}</label
>
<input
autocomplete="off"
placeholder={$_("city")}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="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-neutral-800 rounded-md p-2"
/>
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-city-is-required")}
</span>
{/if}
</div>
{/if}
</section>
{:catch error}
<PromiseError {error} />
<PromiseError {error} />
{/await}

View File

@@ -18,7 +18,7 @@
{:else}
<a
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 border border-current bg-green-700 text-white mr-1"
>{$_("fixed-donation")}:
{(donation.amount / 100)
.toFixed(2)

View File

@@ -9,61 +9,61 @@
</script>
<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">
{$_("donors")}
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("add-donor")}
</button>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")}
<button
on:click={() => {
const data = current_donors
.filter((d) => d.receiptNeeded === true)
.map(function (d) {
d.address.address2 =
d.address.address2 === "" ? "" : " " + d.address.address2;
const address = `${d.address.address1}${d.address.address2}, ${d.address.postalcode} ${d.address.city}, ${d.address.country}`;
return [
d.firstname,
d.middlename,
d.lastname,
d.paidDonationAmount,
address,
];
});
let csv = `${$_("csv_import__firstname")};${$_(
"csv_import__middlename"
)};${$_("csv_import__lastname")};${$_(
"total_donation_amount_in_eur"
)};${$_("address")}\n`;
data.forEach(function (row) {
csv += row.join(";");
csv += "\n";
</h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>
{$_("add-donor")}
</button>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")}
<button
on:click={() => {
const data = current_donors
.filter((d) => d.receiptNeeded === true)
.map(function (d) {
d.address.address2 =
d.address.address2 === "" ? "" : " " + d.address.address2;
const address = `${d.address.address1}${d.address.address2}, ${d.address.postalcode} ${d.address.city}, ${d.address.country}`;
return [
d.firstname,
d.middlename,
d.lastname,
(d.paidDonationAmount/100).toFixed(2),
address,
];
});
let hiddenElement = document.createElement("a");
hiddenElement.href = "data:text/csv;charset=utf-8," + encodeURI(csv);
hiddenElement.target = "_blank";
hiddenElement.download = `${$_(
"filename_sponsoringquittungsliste"
)}.csv`;
hiddenElement.click();
hiddenElement.remove();
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("sponsoring-quittungs-liste_herunterladen")}
</button>
{/if}
</span>
let csv = `${$_("csv_import__firstname")};${$_(
"csv_import__middlename"
)};${$_("csv_import__lastname")};${$_(
"total_donation_amount_in_eur"
)};${$_("address")}\n`;
data.forEach(function (row) {
csv += row.join(";");
csv += "\n";
});
let hiddenElement = document.createElement("a");
hiddenElement.href = "data:text/csv;charset=utf-8," + encodeURI(csv);
hiddenElement.target = "_blank";
hiddenElement.download = `${$_(
"filename_sponsoringquittungsliste"
)}.csv`;
hiddenElement.click();
hiddenElement.remove();
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>
{$_("sponsoring-quittungs-liste_herunterladen")}
</button>
{/if}
<DonorsOverview bind:current_donors bind:addDonors />
</section>

View File

@@ -174,7 +174,7 @@
toast.loading($_("deleting-donor"));
await DonorService.donorControllerRemove(event.detail.id, true);
toast.dismiss();
toast($_("donor-deleted"));
toast.success($_("donor-deleted"));
current_donors = current_donors.filter((d) => d.id !== event.detail.id);
active_deletes = active_deletes.filter((a) => a.id !== event.detail.id);
options.update((options) => ({
@@ -202,7 +202,7 @@
bind:value={searchvalue}
placeholder={$_("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
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"

View File

@@ -1,210 +1,196 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
export let modal_open;
(function () {
document.onkeydown = function (e) {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
};
})();
const license_promise = fetch("/licenses.json");
let licenses = [];
$: currentlicense = "";
$: licensetext = "";
license_promise
.then((response) => response.json())
.then((json) => {
licenses = json;
});
let modal_open = false;
(function () {
document.onkeydown = function (e) {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
};
})();
const license_promise = fetch("/licenses.json");
let licenses = [];
$: currentlicense = "";
$: licensetext = "";
license_promise
.then((response) => response.json())
.then((json) => {
licenses = json;
});
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
fill="currentColor"
class="h-6 w-6 text-blue-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="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 class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium">
{$_("read-license")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">{currentlicense}</p>
</div>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">{licensetext}</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("close")}
</button>
</div>
</div>
</div>
</div>
<div
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<div
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
fill="currentColor"
class="h-6 w-6 text-blue-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="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 class="mt-3 sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium">
{$_("read-license")}
</h3>
<div class="mb-6">
<p class="text-sm text-gray-500">{currentlicense}</p>
</div>
<div class="mb-6">
<p class="text-sm text-gray-500">{licensetext}</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm"
>
{$_("close")}
</button>
</div>
</div>
</div>
</div>
{/if}
<!-- /// -->
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8">
<h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"
>
{$_("about")}
🧾
</h1>
<p
class="mt-2 max-w-xl mx-auto text-xl lg:max-w-3xl lg:text-2xl text-gray-300"
>
Lauf für Kaya!
<strong class="text-white font-medium">
{$_("by")}
<a href="https://odit.services" class="underline">ODIT.Services</a>
</strong>
<br />
<span class="text-lg">{$_("lfk-is-os")}</span>
</p>
</div>
</div>
<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">
<h2 class="text-4xl font-display font-semibold md:text-5xl">
{$_("credits")}
</h2>
<div class="max-w-3xl mx-auto text-xl leading-8 font-medium mt-8">
<p class="text-center">{$_("oss_credit_description")}</p>
</div>
<div class="w-screen leading-8 pl-5 mt-5">
{#await license_promise}
<p class="text-center w-full">{$_("licenses-are-being-loaded")}</p>
{:then}
<table>
<thead class="border-b border-gray-400">
<tr class="odd:bg-white even:bg-gray-100">
<th>{$_("dependency_name")}</th>
<th>{$_("license")}</th>
<th>{$_("repo_link")}</th>
<th>{$_("installed-version")}</th>
<th>{$_("author")}</th>
</tr>
</thead>
<tbody>
{#each licenses as l}
<tr class="odd:bg-white even:bg-gray-100">
<td>{l.name}</td>
<td>
{l.license || "?"}<br /><button
class="underline cursor-pointer"
on:click={() => {
modal_open = true;
currentlicense = l.name + "@" + l.version;
licensetext =
l.licensetext || $_("no-license-text-could-be-found");
}}>{$_("read-license")}</button
>
</td>
<td>
{(l.repo?.url || l.repo)
.replace("git+", "")
.replace("git://", "")}
</td>
<td>{l.version || "?"}</td>
<td>{l.author?.name || l.author || "?"}</td>
</tr>
{/each}
</tbody>
</table>
{:catch error}
<div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"
>
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_("general_promise_error")}</b>
{error}
</span>
</div>
{/await}
</div>
<div class="w-full leading-8 mt-8">
<p class="text-xl font-medium">{$_("icon-image-credits")}</p>
<ul class="list-disc">
<li>
<a
class="underline"
target="_blank"
rel="noopener noreferrer"
href="https://storyset.com">https://storyset.com</a
>
</li>
<li>
<a
class="underline"
target="_blank"
rel="noopener noreferrer"
href="https://undraw.co">https://undraw.co</a
>
</li>
<li>
<a
class="underline"
target="_blank"
rel="noopener noreferrer"
href="https://remixicon.com">https://remixicon.com</a
>
</li>
</ul>
</div>
</div>
</div>
<section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("about")}
</h4>
<p class="mt-2 mb-2">
Lauf für Kaya!
<strong class="font-medium">
{$_("by")}
<a href="https://odit.services" class="underline">ODIT.Services</a>
</strong>
<br />
<span>{$_("lfk-is-os")}</span>
</p>
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("credits")}
</h4>
<p class="text-left">{$_("oss_credit_description")}</p>
<div class="mt-5 overflow-x-auto">
{#await license_promise}
<p>{$_("licenses-are-being-loaded")}</p>
{:then}
<table class="font-mono">
<thead class="border-b border-gray-400">
<tr class="odd:bg-white even:bg-gray-100">
<th>{$_("dependency_name")}</th>
<th>{$_("license")}</th>
<th>{$_("repo_link")}</th>
<th>{$_("installed-version")}</th>
<th>{$_("author")}</th>
</tr>
</thead>
<tbody>
{#each licenses as l}
<tr class="odd:bg-white even:bg-gray-100 *:p-2">
<td>{l.name}</td>
<td>
<button
class="underline cursor-pointer"
on:click={() => {
modal_open = true;
currentlicense = l.name + "@" + l.version;
licensetext =
l.licensetext || $_("no-license-text-could-be-found");
}}>{l.license || "?"}</button
>
</td>
<td>
{(l.repo?.url || l.repo)
.replace("git+", "")
.replace("git://", "")}
</td>
<td>{l.version || "?"}</td>
<td>{l.author?.name || l.author || "?"}</td>
</tr>
{/each}
</tbody>
</table>
{:catch error}
<div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"
>
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_("general_promise_error")}</b>
{error}
</span>
</div>
{/await}
</div>
<div class="w-full mt-8">
<p class="font-medium">{$_("icon-image-credits")}</p>
<ul class="list-disc ml-6">
<li>
<a
class="underline"
target="_blank"
rel="noopener noreferrer"
href="https://storyset.com">https://storyset.com</a
>
</li>
<li>
<a
class="underline"
target="_blank"
rel="noopener noreferrer"
href="https://undraw.co">https://undraw.co</a
>
</li>
<li>
<a
class="underline"
target="_blank"
rel="noopener noreferrer"
href="https://remixicon.com">https://remixicon.com</a
>
</li>
</ul>
</div>
</section>

View File

@@ -0,0 +1,123 @@
<script>
import { RunnerCardService, RunnerService } from "@odit/lfk-client-js";
import QrCodeScanner from "./QrCodeScanner.svelte";
let state = "scan_runner";
let runnerinfo = { id: 0, firstname: "", lastname: "" };
let cardCode = "";
$: scannerActive = state.includes("scan");
</script>
<div class="p-4">
<h3 class="text-3xl font-bold">Card Assignment for Mobile</h3>
<!-- <p>state</p>
<p>{state}</p>
<p>scannerActive</p>
<p>{scannerActive}</p> -->
{#if state.includes("scan_")}
<!-- -->
{#if state === "scan_runner"}
<h3 class="text-xl font-bold">Scan Runner (Selfservice QR)</h3>
{/if}
{#if state === "scan_card"}
<h3 class="text-xl font-bold">Runner Scanned</h3>
<p>{runnerinfo.firstname} {runnerinfo.lastname} [#{runnerinfo.id}]</p>
<h3 class="text-xl font-bold">Scan Card (Code 128 Barcode)</h3>
{/if}
<QrCodeScanner
paused={!scannerActive}
on:detect={(e) => {
console.log({ type: "DETECT", code: e.detail.decodedText });
if (state === "scan_runner") {
if (
e.detail.decodedText.includes(
"https://portal.lauf-fuer-kaya.de/profile/"
)
) {
const runnerID = JSON.parse(
atob(
e.detail.decodedText
.replace("https://portal.lauf-fuer-kaya.de/profile/", "")
.split(".")[1]
)
).id;
new Audio("/beep.mp3").play();
RunnerService.runnerControllerGetOne(runnerID).then((runner) => {
console.log(runner);
runnerinfo = runner;
});
state = "scan_card";
}
}
if (state === "scan_card") {
if (
!e.detail.decodedText.includes(
"https://portal.lauf-fuer-kaya.de/profile/"
)
) {
cardCode = e.detail.decodedText;
new Audio("/beep.mp3").play();
state = "assigning";
RunnerCardService.runnerCardControllerGetAll().then((cards) => {
console.log(cards);
const card = cards.find((c) => c.code === cardCode);
if (card) {
console.log("card found", card);
RunnerCardService.runnerCardControllerPut(card.id, {
enabled: true,
id: card.id,
runner: runnerinfo.id,
}).then(() => {
state = "done";
});
} else {
state = "error_card_404";
}
});
}
}
}}
width={320}
height={320}
class="w-full max-w-sm bg-neutral-300 rounded-lg overflow-hidden"
/>
{#if state === "scan_card"}
<button
on:click={() => {
state = "scan_runner";
runnerinfo = { id: 0, firstname: "", lastname: "" };
cardCode = "";
}}
type="button"
class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-red-100 text-red-800 hover:bg-red-200 focus:outline-hidden focus:bg-red-200 disabled:opacity-50 disabled:pointer-events-none dark:text-red-500 dark:bg-red-800/30 dark:hover:bg-red-800/20 dark:focus:bg-red-800/20 w-full mt-2"
>
Cancel
</button>
{/if}
<!-- -->
{:else}
<!-- -->
{#if state === "assigning"}
<p>Assigning Card {cardCode}</p>
<p>Please wait a moment while we assign the card...</p>
{/if}
{#if state === "done"}
<p>
Assigned Card {cardCode} to {runnerinfo.firstname}
{runnerinfo.lastname} [#{runnerinfo.id}] ✅
</p>
<button
on:click={() => {
// state = "scan_runner";
// runnerinfo = { id: 0, firstname: "", lastname: "" };
// cardCode = "";
location.reload();
}}
type="button"
class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-100 text-blue-800 hover:bg-blue-200 focus:outline-hidden focus:bg-blue-200 disabled:opacity-50 disabled:pointer-events-none dark:text-blue-500 dark:bg-blue-800/30 dark:hover:bg-blue-800/20 dark:focus:bg-blue-800/20 w-full mt-2"
>
Done
</button>
{/if}
<!-- -->
{/if}
</div>

View File

@@ -0,0 +1,84 @@
<script>
import { onMount, createEventDispatcher } from "svelte";
import {
Html5QrcodeScanner,
Html5QrcodeScanType,
Html5QrcodeSupportedFormats,
Html5QrcodeScannerState,
} from "html5-qrcode";
export let width;
export let height;
export let paused = false;
const dispatch = createEventDispatcher();
function onScanSuccess(decodedText, decodedResult) {
if (!paused) {
dispatch("detect", { decodedText });
}
}
// usually better to ignore and keep scanning
function onScanFailure(message) {
if (!paused) {
dispatch("error", { message });
}
}
let scanner;
onMount(() => {
scanner = new Html5QrcodeScanner(
"qr-scanner",
{
fps: 10,
rememberLastUsedCamera: true,
qrbox: { width, height },
aspectRatio: 1,
supportedScanTypes: [Html5QrcodeScanType.SCAN_TYPE_CAMERA],
formatsToSupport: [
Html5QrcodeSupportedFormats.CODE_39,
Html5QrcodeSupportedFormats.EAN_8,
Html5QrcodeSupportedFormats.EAN_13,
Html5QrcodeSupportedFormats.QR_CODE,
Html5QrcodeSupportedFormats.CODE_128,
],
},
false // non-verbose
);
scanner.render(onScanSuccess, onScanFailure);
});
// pause/resume scanner to avoid unintended scans
$: togglePause(paused);
function togglePause(paused) {
if (paused && scanner?.getState() === Html5QrcodeScannerState.SCANNING) {
scanner?.pause();
} else if (scanner?.getState() === Html5QrcodeScannerState.PAUSED) {
scanner?.resume();
}
}
</script>
<div id="qr-scanner" class={$$props.class} />
<style>
/* Hide unwanted icons */
#qr-scanner :global(img[alt="Info icon"]),
#qr-scanner :global(img[alt="Camera based scan"]) {
display: none;
}
/* Change camera permission button text */
#qr-scanner :global(#html5-qrcode-button-camera-permission) {
visibility: hidden;
}
#qr-scanner :global(#html5-qrcode-button-camera-permission::after) {
position: absolute;
inset: auto 0 0;
display: block;
content: "Allow camera access";
visibility: visible;
padding: 10px 0;
}
</style>

View File

@@ -59,14 +59,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -79,15 +79,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -102,18 +102,18 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-user-group")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-required-information-for-creating-a-new-user-group"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="firstname"
@@ -130,7 +130,7 @@
bind:value={name_input_value}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isNameValid}
<span
@@ -152,20 +152,20 @@
bind:value={description_input_value}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
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-neutral-800 rounded-md p-2"
/>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create")}
</button>
@@ -174,7 +174,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

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

View File

@@ -5,6 +5,7 @@
CreatePermission,
UserGroupService,
} from "@odit/lfk-client-js";
import toast from 'svelte-french-toast'
import PromiseError from "../base/PromiseError.svelte";
export let params;
@@ -49,7 +50,7 @@
);
});
grantedPermissions_initial = grantedPermissions;
toast($_("permissions-updated"));
toast.success($_("permissions-updated"));
});
}
Object.values(CreatePermission.target).forEach((t) => {
@@ -132,27 +133,25 @@
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold">
{$_("permissions")}:
{original_data.name}
<span>
<div class="mb-4 text-3xl font-extrabold">
<div>
{#if promises.length === 0}
<button
disabled={save_enabled}
class:opacity-50={save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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 mb-1 lg:mb-0"
>{$_("save-changes")}</button
>
{:else}
<button
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-yellow-600 text-base font-medium text-white hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 sm:ml-3 sm:w-auto sm:text-sm"
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}
</span>
</div>
</div>
<!-- -->
<div class="flex flex-wrap -mx-1 overflow-hidden">
@@ -192,7 +191,7 @@
}
}}
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"
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>
@@ -232,7 +231,7 @@
}
}}
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"
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>

View File

@@ -8,20 +8,20 @@
</script>
<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">
{$_("user-groups")}
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("add-user-group")}
</button>
{/if}
</span>
</h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm"
>
{$_("add-user-group")}
</button>
{/if}
<UserGroupsOverview bind:current_groups />
</section>

View File

@@ -31,7 +31,7 @@
bind:value={searchvalue}
placeholder={$_("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
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"

View File

@@ -25,7 +25,7 @@
$: address_input2_value = "";
$: address_zipcode_value = "";
$: address_city_value = "";
$: address_checked = true;
$: address_checked = false;
let address_input1;
let address_input2;
@@ -82,14 +82,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -102,15 +102,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -124,18 +124,18 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-organization")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-required-information-to-add-a-new-organization"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="firstname"
@@ -153,7 +153,7 @@
bind:this={name_input_dom}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
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-neutral-800 rounded-md p-2"
/>
{#if !isOrgnameValid}
<span
@@ -174,7 +174,7 @@
/>
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-medium text-gray-700"
<label for="comments" class="font-semibold text-gray-700"
>{$_("address")}</label
>
</div>
@@ -196,7 +196,7 @@
bind:this={address_input1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isAddress1Valid}
<span
@@ -219,10 +219,10 @@
bind:this={address_input2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<div class="col-span-2">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700"
@@ -238,7 +238,7 @@
bind:this={address_zipcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !iszipcodevalid}
<span
@@ -248,7 +248,7 @@
</span>
{/if}
</div>
<div class="col-span-6">
<div class="col-span-4">
<label
for="city"
class="block text-sm font-medium text-gray-700"
@@ -264,7 +264,7 @@
bind:this={address_city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !iscityvalid}
<span
@@ -279,13 +279,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create")}
</button>
@@ -294,7 +294,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -1,108 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import { createEventDispatcher } from "svelte";
export let modal_open;
export let delete_org;
const dispatch = createEventDispatcher();
function cancelDelete() {
modal_open = false;
dispatch("cancelDelete", { id: delete_org.id });
}
function deleteOrg() {
RunnerOrganizationService.runnerOrganizationControllerRemove(
delete_org.id,
true
)
.then((resp) => {
toast($_("organization-deleted"));
location.replace("./");
})
.catch((err) => {});
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={cancelDelete}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"
><path
fill="currentColor"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("attention")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"do-you-want-to-delete-the-organization-delete_org-name",
{
values: { orgname: delete_org.name },
}
)}<br />
{$_("all-associated-teams-and-runners-will-be-deleted-too")}
</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={deleteOrg}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("confirm-delete-organization-and-associated-teams-runners")}
</button>
<button
on:click={cancelDelete}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel-keep-organization")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,104 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import { createEventDispatcher } from "svelte";
export let modal_open;
export let delete_org;
const dispatch = createEventDispatcher();
function cancelDelete() {
modal_open = false;
dispatch("cancelDelete", { id: delete_org.id });
}
function deleteOrg() {
RunnerOrganizationService.runnerOrganizationControllerRemove(
delete_org.id,
true
)
.then((resp) => {
toast.success($_("organization-deleted"));
location.replace("./");
})
.catch((err) => {});
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={cancelDelete}
>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<div
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"
><path
fill="currentColor"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"
/></svg
>
</div>
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("do-you-want-to-delete-the-organization-delete_org-name", {
values: { orgname: delete_org.name },
})}
</h3>
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_("all-associated-teams-and-runners-will-be-deleted-too")}
</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
on:click={deleteOrg}
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"
>
{$_("confirm-delete-organization-and-associated-teams-runners")}
</button>
<button
on:click={cancelDelete}
type="button"
class="w-full 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 hidden lg:block"
>
{$_("cancel-keep-organization")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

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

View File

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

View File

@@ -1,54 +1,253 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddOrgModal from "./AddOrgModal.svelte";
export let modal_open = false;
import OrgOverview from "./OrgOverview.svelte";
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
let current_organizations = [];
export let import_modal_open = false;
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
let delete_org = {};
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import store from "../../store";
import OrgsEmptyState from "./OrgsEmptyState.svelte";
import ConfirmOrgDeletionModal from "./ConfirmOrgDeletionModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import toast from "svelte-french-toast";
$: searchvalue = "";
$: active_deletes = [];
$: sponsoring_contracts_show = current_organizations.some(
(r) => r.is_selected === true
);
$: cards_show = current_organizations.some((r) => r.is_selected === true);
$: generate_orgs = current_organizations.filter(
(r) => r.is_selected === true
);
$: certificates_show = current_organizations.some(
(r) => r.is_selected === true
);
let current_organizations = [];
const promise =
RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
current_organizations = val;
}
);
import { _ } from "svelte-i18n";
import AddOrgModal from "./AddOrgModal.svelte";
let delete_modal_open = false;
let modal_open = false;
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
let import_modal_open = false;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_("organizations")}
{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("create-organization")}
</button>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:IMPORT")}
<button
on:click={() => {
import_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("import-runners")}
</button>
{/if}
</span>
<OrgOverview bind:current_organizations />
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("organizations")}
</h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>
{$_("create-organization")}
</button>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:IMPORT")}
<button
on:click={() => {
import_modal_open = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>
{$_("import-runners")}
</button>
{/if}
<ConfirmOrgDeletionModal
on:cancelDelete={(event) => {
delete_modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open={delete_modal_open}
bind:delete_org
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:GET")}
{#await promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("organizations-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then}
{#if current_organizations.length === 0}
<OrgsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_("datatable.search")}
aria-label={$_("datatable.search")}
class="w-full sm:w-auto sm:mt-0 p-2 rounded-md border mb-1 lg:mb-0"
/>
<GenerateSponsoringContracts
bind:sponsoring_contracts_show
bind:generate_orgs
/>
<GenerateRunnerCards bind:cards_show bind:generate_orgs />
<GenerateRunnerCertificates bind:certificates_show bind:generate_orgs />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr class="odd:bg-white even:bg-gray-100">
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
<button
on:click={() => {
const newstate = !current_organizations.some(
(r) => r.is_selected === true
);
current_organizations = current_organizations.map((r) => {
r.is_selected = newstate;
return r;
});
}}
class="underline cursor-pointer select-none"
>{#if current_organizations.some((r) => r.is_selected === true)}
{$_("deselect-all")}
{:else}{$_("select-all")}{/if}
</button>
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("name")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("address")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("contact")}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_("action")}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_organizations as o}
{#if Object.values(o)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr
class="odd:bg-white even:bg-gray-100"
data-rowid="org_{o.id}"
>
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={o.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="text-sm font-medium text-gray-900">
{o.name}
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="text-sm font-medium text-gray-900">
{#if o.address.address1 !== null}
{o.address.address1}<br />
<!-- {o.address.address2 || ''}<br /> -->
{o.address.postalcode}
{o.address.city}
{o.address.country}
{/if}
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="text-sm font-medium text-gray-900">
{#if o.contact}
<a
href="../contacts/{o.contact.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 border border-current"
>{o.contact.firstname}
{o.contact.middlename || ""}
{o.contact.lastname}</a
>
{:else}{$_("no-contact-specified")}{/if}
</div>
</div>
</td>
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a
href="./{o.id}"
class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:DELETE")}
<button
on:click={() => {
active_deletes[o.id] = true;
delete_modal_open = true;
delete_org = o;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
</td>
</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}
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:CREATE")}
<AddOrgModal bind:current_organizations bind:modal_open />
<ImportRunnerModal
on:cancelDelete={(event) => {
import_modal_open = false;
}}
passed_team={{}}
passed_org={{}}
passed_orgs={current_organizations}
opened_from="OrgOverview"
current_runners={[]}
bind:import_modal_open
/>
<AddOrgModal bind:current_organizations bind:modal_open />
<ImportRunnerModal
on:cancelDelete={(event) => {
import_modal_open = false;
}}
passed_team={{}}
passed_org={{}}
passed_orgs={current_organizations}
opened_from="OrgOverview"
bind:import_modal_open
/>
{/if}

View File

@@ -0,0 +1,151 @@
class DocumentServer {
baseUrl: string;
apiKey: string;
constructor(baseUrl: string, apiKey: string) {
this.baseUrl = baseUrl;
this.apiKey = apiKey;
}
async generateCards(cards: any[], locale: string) {
const generateCards = new Array<any>();
for (let i = 0; i < cards.length; i++) {
const card = {
id: cards[i].id,
enabled: cards[i].enabled,
code: cards[i].code,
runner: {
id: cards[i]?.runner?.id,
first_name: cards[i]?.runner?.firstname,
middle_name: cards[i]?.runner?.middlename,
last_name: cards[i]?.runner?.lastname,
group: {
id: cards[i]?.runner?.group.id,
name: cards[i]?.runner?.group.name,
parent_group: {
id: cards[i]?.runner?.group?.parentGroup?.id,
name: cards[i]?.runner?.group?.parentGroup?.name,
},
},
},
};
generateCards.push(card);
}
const response = await fetch(
`${this.baseUrl}/v1/pdfs/cards?key=${this.apiKey}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
locale,
cards: generateCards,
}),
},
);
const blob = await response.blob();
return blob;
}
async generateContracts(runners: any[], locale: string) {
const generateRunners = new Array<any>();
for (let i = 0; i < runners.length; i++) {
console.log(runners[i]);
const card = {
id: runners[i].id,
first_name: runners[i].firstname,
middle_name: runners[i].middlename,
last_name: runners[i].lastname,
group: {
id: runners[i].group.id,
name: runners[i].group.name,
parent_group: {
id: runners[i]?.group?.parentGroup?.id,
name: runners[i]?.group?.parentGroup?.name,
},
},
};
generateRunners.push(card);
}
const response = await fetch(
`${this.baseUrl}/v1/pdfs/contracts?key=${this.apiKey}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
locale,
runners: generateRunners,
}),
},
);
const blob = await response.blob();
return blob;
}
async generateCertificates(runners: any[], locale: string) {
const generateRunners = new Array<any>();
for (let i = 0; i < runners.length; i++) {
const certificate = {
id: runners[i].id,
first_name: runners[i].firstname,
middle_name: runners[i].middlename,
last_name: runners[i].lastname,
self_service_link: runners[i].selfserviceLink,
group: {
id: runners[i].group.id,
name: runners[i].group.name,
parent_group: {
id: runners[i]?.group?.parentGroup?.id,
name: runners[i]?.group?.parentGroup?.name,
},
},
distance: runners[i].distance,
distance_donations: runners[i].distanceDonations.map(
(distanceDonation: any) => {
return {
id: distanceDonation.id,
amount: distanceDonation.amount,
amount_per_distance: distanceDonation.amountPerDistance,
donor: {
id: distanceDonation.donor.id,
first_name: distanceDonation.donor.firstname,
middle_name: distanceDonation.donor.middlename,
last_name: distanceDonation.donor.lastname,
},
};
},
),
};
generateRunners.push(certificate);
}
const response = await fetch(
`${this.baseUrl}/v1/pdfs/certificates?key=${this.apiKey}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
locale,
runners: generateRunners,
}),
},
);
const blob = await response.blob();
return blob;
}
}
export default DocumentServer;

View File

@@ -0,0 +1,81 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { onMount } from "svelte";
export let download_details = "";
export let modal_open;
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<div
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"
/></svg
>
</div>
<div class="mt-3 sm:text-left text-base">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('download_laeuft')}
</h3>
<div class="w-full">
{download_details}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -1,364 +1,197 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
RunnerCardService,
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import {
RunnerCardService,
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import toast from "svelte-french-toast";
import DocumentServer from "./DocumentServer.ts";
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" });
const documentServer = new DocumentServer(
config.baseurl_documentserver,
config.documentserver_key
);
export let cards_show = false;
export let generate_cards = [];
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: cards_dropdown_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "cards:dropdown" &&
e.target.parentNode?.parentNode?.id != "cards:dropdown:menu"
) {
cards_dropdown_open = false;
}
});
export let cards_show = false;
export let generate_cards = [];
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
function generateRunnerCards(locale) {
cards_dropdown_open = false;
function download(blob, fileName) {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
toast.dismiss();
toast.success($_("pdf-successfully-generated"));
}
if (generate_orgs.length > 0) {
generateOrgCards(locale);
} else if (generate_teams.length > 0) {
generateTeamCards(locale);
} else if (generate_runners.length > 0) {
generateRunnersCards(locale);
} else {
generateCards(locale);
}
}
function generateRunnerCards(locale) {
if (generate_orgs.length > 0) {
generateOrgCards(locale);
} else if (generate_teams.length > 0) {
generateTeamCards(locale);
} else if (generate_runners.length > 0) {
generateRunnersCards(locale);
} else {
generateCards(locale);
}
}
function generateCards(locale) {
toast.loading($_("generating-pdf"));
fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(generate_cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("runnercards")}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
toast.dismiss();
toast($_("pdf-successfully-generated"));
})
.catch((err) => {
console.error(err);
});
}
function generateCards(locale) {
toast.loading($_("generating-pdf"));
documentServer
.generateCards(generate_cards, locale)
.then((blob) => {
download(blob, `${$_("runnercards")}-${locale}-${createId()}.pdf`);
})
.catch((err) => {
console.error(err);
});
}
async function generateRunnersCards(locale) {
toast.loading($_("generating-pdf"));
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let cards = [];
for (let runner of generate_runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
if (generate_runners.length == 1) {
a.download = `${$_("runnercards")}_${generate_runners[0].firstname}_${
generate_runners[0].lastname
}-${locale}-${createId()}.pdf`;
} else {
a.download = `${$_("runnercards")}-${locale}-${createId()}.pdf`;
}
document.body.appendChild(a);
a.click();
a.remove();
toast.dismiss();
toast($_("pdf-successfully-generated"));
})
.catch((err) => {});
}
async function generateRunnersCards(locale) {
toast.loading($_("generating-pdf"));
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let cards = [];
for (let runner of generate_runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
documentServer
.generateCards(cards, locale)
.then((blob) => {
let fileName = `${$_("runnercards")}-${locale}-${createId()}.pdf`;
if (generate_runners.length == 1) {
fileName = `${$_("runnercards")}_${generate_runners[0].firstname}_${
generate_runners[0].lastname
}-${locale}-${createId()}.pdf`;
}
download(blob, fileName);
})
.catch((err) => {});
}
async function generateTeamCards(locale) {
toast.loading($_("generating-pdfs"));
let count = 0;
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
for (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let cards = [];
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("runnercards")}_${
t.name
}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_teams.length) {
toast.dismiss();
toast.success($_("pdfs-successfully-generated"));
}
})
.catch((err) => {});
}
}
async function generateTeamCards(locale) {
toast.loading($_("generating-pdfs"));
let count = 0;
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
for (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let cards = [];
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
documentServer
.generateCards(cards, locale)
.then((blob) => {
download(
blob,
`${$_("runnercards")}_${t.name}-${locale}-${createId()}.pdf`
);
})
.catch((err) => {});
}
}
async function generateOrgCards(locale) {
toast.loading($_("generating-pdfs"));
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let count = 0;
let count_orgs = 0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id,
true
);
let cards = [];
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
await fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("runnercards")}_${
o.name
}_direct-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.dismiss();
toast.success($_("pdfs-successfully-generated"));
}
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let cards = [];
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
await fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("runnercards")}_${o.name}_${
t.name
}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (
count === o.teams.length &&
count_orgs === generate_orgs.length
) {
toast.dismiss();
toast($_("pdfs-successfully-generated"));
}
})
.catch((err) => {});
}
}
}
async function generateOrgCards(locale) {
toast.loading($_("generating-pdfs"));
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let count = 0;
let count_orgs = 0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id,
true
);
let cards = [];
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
await documentServer
.generateCards(cards, locale)
.then((blob) => {
download(
blob,
`${$_("runnercards")}_${o.name}_direct-${locale}-${createId()}.pdf`
);
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let cards = [];
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
await documentServer
.generateCards(cards, locale)
.then((blob) => {
download(
blob,
`${$_("runnercards")}_${o.name}_${
t.name
}-${locale}-${createId()}.pdf`
);
})
.catch((err) => {});
}
}
}
</script>
{#if cards_show}
<div id="cards:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
cards_dropdown_open = !cards_dropdown_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true"
>
{$_("generate-runnercards")}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"
/></svg
>
</button>
</div>
{#if cards_dropdown_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10"
id="cards:dropdown:menu"
>
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu"
>
<span class="block w-full text-left px-4 py-2 text-sm text-gray-700"
>{$_("select-language")}</span
>
<button
on:click={() => {
generateRunnerCards("de");
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem"
>
{$_("german")}
</button>
<button
on:click={() => {
generateRunnerCards("en");
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem"
>
{$_("english")}
</button>
</div>
</div>
{/if}
</div>
<button
on:click={() => {
generateRunnerCards("de");
}}
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 mb-1 lg:mb-0"
>
{$_("generate-runnercards")}: DE
</button>
<button
on:click={() => {
generateRunnerCards("en");
}}
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 mb-1 lg:mb-0"
>
{$_("generate-runnercards")}: EN
</button>
{/if}

View File

@@ -4,28 +4,23 @@
DonationService,
RunnerTeamService,
RunnerOrganizationService,
RunnerService
} from "@odit/lfk-client-js";
import { init } from "@paralleldrive/cuid2";
import toast from "svelte-french-toast";
import DocumentServer from "./DocumentServer";
const createId = init({ length: 10, fingerprint: "lfk-frontend" });
const documentServer = new DocumentServer(
config.baseurl_documentserver,
config.documentserver_key
);
export let certificates_show = false;
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: certificates_dropdown_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "certificates:dropdown" &&
e.target.parentNode?.parentNode?.id != "certificates:dropdown:menu"
) {
certificates_dropdown_open = false;
}
});
function generateCertificates(locale) {
certificates_dropdown_open = false;
if (generate_orgs.length > 0) {
generateOrgCertificates(locale);
} else if (generate_teams.length > 0) {
@@ -34,6 +29,17 @@
generateRunnerCertificates(locale);
}
}
function download(blob, fileName) {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
toast.dismiss();
toast.success($_("pdf-successfully-generated"));
}
async function generateRunnerCertificates(locale) {
toast.loading($_("generating-pdf"));
@@ -41,44 +47,21 @@
(await DonationService.donationControllerGetAll()) || [];
let certificateRunners = [];
for (let runner of generate_runners) {
runner.distanceDonations =
const linkRunner = await RunnerService.runnerControllerGetOne(runner.id)
linkRunner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
certificateRunners.push(linkRunner);
}
fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
documentServer
.generateCertificates(certificateRunners, locale)
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
let fileName = `${$_("certificates")}-${locale}.pdf`;
if (generate_runners.length == 1) {
a.download = `${$_("certificates")}_${
fileName = `${$_("certificates")}_${
generate_runners[0].firstname
}_${generate_runners[0].lastname}-${locale}-${createId()}.pdf`;
} else {
a.download = `${$_("certificates")}-${locale}.pdf`;
}
document.body.appendChild(a);
a.click();
a.remove();
toast.dismiss();
toast($_("pdf-successfully-generated"));
download(blob, fileName);
})
.catch((err) => {});
}
@@ -90,7 +73,8 @@
(await DonationService.donationControllerGetAll()) || [];
for (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
t.id,
true
);
let certificateRunners = [];
for (let runner of runners) {
@@ -98,39 +82,14 @@
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
documentServer
.generateCertificates(certificateRunners, locale)
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("certificates")}_${
t.name
}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_teams.length) {
toast.dismiss();
toast.success($_("pdfs-successfully-generated"));
}
download(
blob,
`${$_("certificates")}_${t.name}-${locale}-${createId()}.pdf`
);
})
.catch((err) => {});
}
@@ -148,7 +107,8 @@
let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id,
true
true,
true
);
let certificateRunners = [];
for (let runner of runners) {
@@ -156,44 +116,20 @@
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
await documentServer
.generateCertificates(certificateRunners, locale)
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("certificates")}_${
o.name
}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.dismiss();
toast.success($_("pdfs-successfully-generated"));
}
download(
blob,
`${$_("certificates")}_${o.name}-${locale}-${createId()}.pdf`
);
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
t.id,
true
);
let certificateRunners = [];
for (let runner of runners) {
@@ -201,40 +137,21 @@
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
await documentServer
.generateCertificates(certificateRunners, locale)
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("certificates")}_${o.name}_${
t.name
}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
download(
blob,
`${$_("certificates")}_${o.name}_${
t.name
}-${locale}-${createId()}.pdf`
);
if (
count === o.teams.length &&
count_orgs === generate_orgs.length
) {
toast.dismiss();
toast($_("pdfs-successfully-generated"));
toast.success($_("pdfs-successfully-generated"));
}
})
.catch((err) => {});
@@ -244,69 +161,20 @@
</script>
{#if certificates_show}
<div id="certificates:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
certificates_dropdown_open = !certificates_dropdown_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true"
>
{$_("generate-runner-certificates")}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"
/></svg
>
</button>
</div>
{#if certificates_dropdown_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10"
id="certificates:dropdown:menu"
>
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu"
>
<span class="block w-full text-left px-4 py-2 text-sm text-gray-700"
>{$_("select-language")}</span
>
<button
on:click={() => {
generateCertificates("de");
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem"
>
{$_("german")}
</button>
<button
on:click={() => {
generateCertificates("en");
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem"
>
{$_("english")}
</button>
</div>
</div>
{/if}
</div>
<button
on:click={() => {
generateCertificates("de");
}}
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 mb-1 lg:mb-0"
>
{$_("generate-runner-certificates")}: DE
</button>
<button
on:click={() => {
generateCertificates("en");
}}
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 mb-1 lg:mb-0"
>
{$_("generate-runner-certificates")}: EN
</button>
{/if}

View File

@@ -1,283 +1,163 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import {
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import DocumentServer from "./DocumentServer";
import { init } from "@paralleldrive/cuid2";
import toast from "svelte-french-toast";
import DownloadProgressModal from "./DownloadProgressModal.svelte";
const createId = init({ length: 10, fingerprint: "lfk-frontend" });
const documentServer = new DocumentServer(
config.baseurl_documentserver,
config.documentserver_key
);
import { init } from "@paralleldrive/cuid2";
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 generate_orgs = [];
export let generate_teams = [];
//
export let download_modal_open = false;
export let download_details = "";
export let sponsoring_contracts_show = false;
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: sponsoring_contracts_download_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
function generateSponsoringContract(locale) {
download_modal_open = true;
if (generate_orgs.length > 0) {
generateOrgContracts(locale);
} else if (generate_teams.length > 0) {
generateTeamContracts(locale);
} else {
generateRunnerContracts(locale);
}
}
function download(blob, fileName) {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
toast.dismiss();
toast.success($_("pdf-successfully-generated"));
}
function generateSponsoringContract(locale) {
sponsoring_contracts_download_open = false;
async function generateTeamContracts(locale) {
toast.loading($_("generating-pdfs"));
let totalCount = generate_teams.length;
let count = 0;
for (const t of generate_teams) {
count++;
download_details = `${t.parentGroup.name} > ${t.name}`;
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
await documentServer
.generateContracts(runners, locale)
.then((blob) => {
download(
blob,
`${$_("sponsorings")}_${t.name}-${locale}-${createId()}.pdf`
);
if (count === totalCount) {
download_modal_open = false;
}
})
.catch((err) => {});
}
}
if (generate_orgs.length > 0) {
generateOrgContracts(locale);
} else if (generate_teams.length > 0) {
generateTeamContracts(locale);
} else {
generateRunnerContracts(locale);
}
}
async function generateOrgContracts(locale) {
toast.loading($_("generating-pdf"));
let totalCount = 0;
for (const o of generate_orgs) {
totalCount++;
for (const t of o.teams) {
totalCount++;
}
}
console.log({ totalCount });
let count = 0;
for (const o of generate_orgs) {
count++;
let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id,
true
);
download_details = o.name;
await documentServer
.generateContracts(runners, locale)
.then((blob) => {
download(
blob,
`${$_("sponsorings")}_${o.name}_direct-${locale}-${createId()}.pdf`
);
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
download_details = `${o.name} > ${t.name}`;
await documentServer
.generateContracts(runners, locale)
.then((blob) => {
download(
blob,
`${$_("sponsorings")}_${o.name}_${
t.name
}-${locale}-${createId()}.pdf`
);
console.log({ count });
if (count === totalCount) {
download_modal_open = false;
}
})
.catch((err) => {});
}
}
}
async function generateTeamContracts(locale) {
toast.loading($_("generating-pdfs"));
let count = 0;
for (const t of generate_teams) {
count++;
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
fetch(
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("sponsorings")}_${
t.name
}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_teams.length) {
toast.dismiss();
toast.success($_("pdfs-successfully-generated"));
}
})
.catch((err) => {});
}
}
async function generateOrgContracts(locale) {
toast.loading($_("generating-pdf"));
let count_orgs = 0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id,
true
);
await fetch(
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("sponsorings")}_${
o.name
}_direct-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.dismiss();
toast.success($_("pdfs-successfully-generated"));
}
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
await fetch(
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("sponsorings")}_${o.name}_${
t.name
}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (
count === o.teams.length &&
count_orgs === generate_orgs.length
) {
toast.dismiss();
toast($_("pdfs-successfully-generated"));
}
})
.catch((err) => {});
}
}
}
function generateRunnerContracts(locale) {
toast.loading($_("generating-pdf"));
fetch(
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(generate_runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.dismiss();
toast.error($_("pdf-generation-failed"));
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
if (generate_runners.length == 1) {
a.download = `${$_("sponsorings")}_${generate_runners[0].firstname}_${
generate_runners[0].lastname
}-${locale}-${createId()}.pdf`;
}
a.download = `${$_("sponsorings")}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
toast.dismiss();
toast($_("pdf-successfully-generated"));
})
.catch((err) => {
console.error(err);
});
}
async function generateRunnerContracts(locale) {
toast.loading($_("generating-pdf"));
await documentServer
.generateContracts(generate_runners, locale)
.then((blob) => {
let fileName = `${$_("sponsorings")}-${locale}-${createId()}.pdf`;
if (generate_runners.length == 1) {
fileName = `${$_("sponsorings")}_${generate_runners[0].firstname}_${
generate_runners[0].lastname
}-${locale}-${createId()}.pdf`;
}
download(blob, fileName);
download_modal_open = false;
})
.catch((err) => {
console.error(err);
});
}
</script>
{#if sponsoring_contracts_show}
<div id="sponsoring:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
sponsoring_contracts_download_open =
!sponsoring_contracts_download_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true"
>
{$_("generate-sponsoring-contracts")}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"
/></svg
>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10"
id="sponsoring:dropdown:menu"
>
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu"
>
<span class="block w-full text-left px-4 py-2 text-sm text-gray-700"
>{$_("select-language")}</span
>
<button
on:click={() => {
generateSponsoringContract("de");
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem"
>
{$_("german")}
</button>
<button
on:click={() => {
generateSponsoringContract("en");
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem"
>
{$_("english")}
</button>
</div>
</div>
{/if}
</div>
<DownloadProgressModal {download_details} modal_open={download_modal_open} />
<button
on:click={() => {
generateSponsoringContract("de");
}}
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 mb-1 lg:mb-0"
>
{$_("generate-sponsoring-contracts")}: DE
</button>
<button
on:click={() => {
generateSponsoringContract("en");
}}
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 mb-1 lg:mb-0"
>
{$_("generate-sponsoring-contracts")}: EN
</button>
{/if}

View File

@@ -1,92 +0,0 @@
<h3 class="text-lg">Standard Avatars</h3>
<div class="relative rounded-full w-4 h-4">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div>
<div class="relative rounded-full w-8 h-8">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div>
<div class="relative rounded-full w-12 h-12">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div>
<div class="relative rounded-full w-16 h-16">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div>
<div class="relative rounded-full w-20 h-20">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div>
<div class="relative rounded-full w-24 h-24">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover"
/>
</div>
<h3 class="text-lg">Status Avatars</h3>
<div class="relative rounded-full w-4 h-4">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
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>
<div class="relative rounded-full w-8 h-8">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
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>
<div class="relative rounded-full w-12 h-12">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
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>
<div class="relative rounded-full w-16 h-16">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
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>
<div class="relative rounded-full w-20 h-20">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
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>
<div class="relative rounded-full w-24 h-24">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
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>

View File

@@ -1,65 +0,0 @@
<h3 class="text-lg">badges</h3>
<span
class="text-sm font-medium bg-green-100 py-1 px-2 rounded text-green-500 align-middle"
>Paid</span
>
<span
class="text-sm font-medium bg-red-100 py-1 px-2 rounded text-red-500 align-middle"
>Overdue</span
>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-blue-600"
>Primary</span
>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-600"
>Secondary</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-red-600"
>Danger</span
>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-yellow-400"
>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>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-blue-600">
Primary
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-600">
Secondary
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-green-600">
Success
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-red-600">
Danger
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-yellow-400">
Warning
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-indigo-300">
Info
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-gray-200">
Light<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-900">
Dark
<span class="ml-2 text-base cursor-pointer">×</span>
</span>

View File

@@ -1,59 +0,0 @@
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="mr-2 flex items-center">
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" /></svg
>
</li>
<li class="flex items-center">
<a class="mr-2" href="/">Home</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
</li>
<li class="flex items-center">
<a class="mr-2" href="/">Second level</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
</li>
<li class="flex items-center">
<a class="mr-2" href="/">Third level</a>
</li>
</ol>
</nav>
</div>
</div>

View File

@@ -1,80 +0,0 @@
<script>
import Avatars from "./Avatars.svelte";
import Badges from "./Badges.svelte";
import BreadcrumbNav from "./BreadcrumbNav.svelte";
import FileUpload from "./FileUpload.svelte";
import Pagination from "./Pagination.svelte";
import Table from "./Table.svelte";
import Tabs from "./Tabs.svelte";
import Tags from "./Tags.svelte";
</script>
<div class="border-4 border-dashed rounded h-96 mb-4" />
<div class="mb-8">
<FileUpload />
</div>
<div class="mb-8">
<Tabs />
</div>
<div class="mb-8">
<Tags />
</div>
<div class="mb-8">
<Badges />
</div>
<div class="mb-8">
<Avatars />
</div>
<Pagination />
<div class="mb-8">
<Table />
</div>
<div 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-col">
<div class="text-sm font-light text-grey-500">Regular</div>
<div class="text-sm font-bold"><span>Text inputs</span></div>
</div>
</div>
<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="form-element">
<div class="form-label">Label</div>
<input
name="name"
type="text"
class="form-input"
placeholder="Enter something..."
/>
<div class="form-hint">This is a hint</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div class="form-element">
<div class="form-label">First name</div>
<input
name="name"
type="text"
class="form-input form-input-invalid"
placeholder="john@example.com"
/>
<div class="form-error">First name is required</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div class="form-element">
<div class="form-label">First name</div>
<input
name="name"
type="text"
class="form-input form-input-valid"
placeholder="john@example.com"
/>
<div class="form-success">First name is valid</div>
</div>
</div>
</div>
</div>
<div class="mb-8">
<BreadcrumbNav />
</div>

View File

@@ -1,643 +0,0 @@
<div class="w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between mb-6">
<div class="flex flex-col">
<div class="text-sm font-light text-grey-500">Conversions</div>
<div class="text-sm font-bold"><span>This year</span></div>
</div>
<div class="relative">
<button
class="btn btn-default btn-circle btn-icon bg-transparent hover:bg-transparent active:bg-transparent relative"
><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
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="flex flex-col w-full">
<ul class="list-none">
<li>
<a
class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100"
href="/">Today</a
>
</li>
<li>
<a
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>
<li>
<a
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>
<li>
<a
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>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="flex flex-row w-full">
<div style="width:100%;height:240px">
<div class="recharts-responsive-container" style="width:100%;height:100%">
<div
class="recharts-wrapper"
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>
<clipPath id="recharts3-clip">
<rect x="40" y="10" height="190" width="654" />
</clipPath>
</defs>
<g
class="recharts-layer recharts-cartesian-axis recharts-xAxis xAxis"
>
<g class="recharts-cartesian-axis-ticks">
<g class="recharts-layer recharts-cartesian-axis-tick"
><text
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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</text></g
>
</g>
</g>
<g
class="recharts-layer recharts-cartesian-axis recharts-yAxis yAxis"
>
<g class="recharts-cartesian-axis-ticks">
<g class="recharts-layer recharts-cartesian-axis-tick"
><text
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>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</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"
text-anchor="end"
>
<tspan x="32" dy="0.355em">130</tspan>
</text></g
>
<g class="recharts-layer recharts-cartesian-axis-tick"
><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>
</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"
text-anchor="end"
>
<tspan x="32" dy="0.355em">260</tspan>
</text></g
>
</g>
</g>
<g class="recharts-layer recharts-bar">
<g class="recharts-layer recharts-bar-rectangles">
<g class="recharts-layer">
<g class="recharts-layer recharts-bar-rectangle">
<path
fill="#90caf9"
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 class="recharts-layer recharts-bar-rectangle">
<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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#90caf9"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#90caf9"
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 class="recharts-layer recharts-bar-rectangle">
<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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#90caf9"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#90caf9"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#90caf9"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#90caf9"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#90caf9"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#90caf9"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#90caf9"
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 class="recharts-layer recharts-bar">
<g class="recharts-layer recharts-bar-rectangles">
<g class="recharts-layer">
<g class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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 class="recharts-layer recharts-bar-rectangle">
<path
fill="#1e88e5"
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>
</svg>
<div
class="recharts-tooltip-wrapper"
style="pointer-events: none; visibility: hidden; position: absolute; top: 0px; transform: translate(538.875px, 126px);"
/>
</div>
<div
style="position:absolute;width:0;height:0;visibility:hidden;display:none"
/>
</div>
</div>
</div>
</div>

View File

@@ -1,120 +0,0 @@
<!-- This example requires Tailwind CSS v2.0+ -->
<div
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">
<a
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"
>
Previous
</a>
<a
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"
>
Next
</a>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p class="text-sm text-gray-700">
Showing
<span class="font-medium">1</span>
to
<span class="font-medium">10</span>
of
<span class="font-medium">97</span>
results
</p>
</div>
<div>
<nav
class="relative z-0 inline-flex shadow-sm -space-x-px"
aria-label="Pagination"
>
<a
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"
>
<span class="sr-only">Previous</span>
<!-- Heroicon name: chevron-left -->
<svg
class="h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
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"
clip-rule="evenodd"
/>
</svg>
</a>
<a
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"
>
1
</a>
<a
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"
>
2
</a>
<a
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"
>
3
</a>
<span
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700"
>
...
</span>
<a
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"
>
8
</a>
<a
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"
>
9
</a>
<a
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"
>
10
</a>
<a
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"
>
<span class="sr-only">Next</span>
<!-- Heroicon name: chevron-right -->
<svg
class="h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
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"
clip-rule="evenodd"
/>
</svg>
</a>
</nav>
</div>
</div>
</div>

View File

@@ -1,289 +0,0 @@
<div class="min-h-screen w-full p-4">
<div class="section-title w-full mb-6 pt-3">
<div class="flex flex-row items-center justify-between mb-4">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">Pages</div>
<div class="text-xl font-bold">User profile</div>
</div>
</div>
</div>
<div 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-shrink-0 w-24">
<img
src="/images/faces/m1.png"
alt="media"
class="shadow rounded-full h-20 w-20 shadow-outline mb-2"
/>
</div>
<div class="py-2 px-2">
<p class="text-base font-bold whitespace-no-wrap">Lucas Smith</p>
<p class="text-sm text-grey-500 whitespace-no-wrap">
Vital Database Dude
</p>
<div
class="flex flex-row items-center justify-start w-full py-1 space-x-2"
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="stroke-current text-xl text-twitter"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/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"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="stroke-current text-xl text-facebook"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/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"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="stroke-current text-xl text-instagram"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><rect x="2" 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" />
<line x1="17.5" y1="6.5" x2="17.5" y2="6.5" /></svg
>
</div>
</div>
<div class="ml-auto flex-shrink-0 space-x-2 hidden lg:flex">
<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"
>Follow</button
>
</div>
</div>
<div class="flex flex-wrap">
<div class="w-full p-4">
<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-none">
<button class="tab tab-underline tab-active" type="button"
>Account settings</button
>
</div>
<div class="flex-none">
<button class="tab tab-underline" type="button"
>Email preferences</button
>
</div>
<div class="flex-none">
<button class="tab tab-underline" type="button"
>Security settings</button
>
</div>
</div>
<div class="tab-content block">
<div class="py-4 w-full lg:w-1/2">
<div class="flex flex-col">
<form class="form flex flex-wrap w-full">
<div class="w-full">
<div class="form-element">
<div class="form-label">First name</div>
<input
name="first-name"
type="text"
class="form-input"
placeholder="Enter you first name"
/>
</div>
<div class="form-element">
<div class="form-label">Last name</div>
<input
name="last-name"
type="text"
class="form-input"
placeholder="Enter you last name"
/>
</div>
<div class="form-element">
<div class="form-label">Email address</div>
<input
name="email"
type="email"
class="form-input"
placeholder="Enter you email address"
/>
</div>
<div class="form-element">
<div class="form-label">Company</div>
<input
name="company"
type="text"
class="form-input"
placeholder="Enter you company"
/>
</div>
<div class="form-element">
<div class="form-label">Position</div>
<input
name="position"
type="text"
class="form-input"
placeholder="Enter you position"
/>
</div>
<div class="form-element">
<div class="form-label">Language</div>
<select name="language" class="form-select"
><option>Select language</option>
<option value="english">English</option>
<option value="spanish">Spanish</option>
<option value="portuguese">Portuguese</option></select
>
</div>
</div>
<input
type="submit"
class="btn btn-default bg-blue-500 hover:bg-blue-600 text-white btn-rounded"
/>
</form>
</div>
</div>
</div>
<div class="tab-content hidden">
<div class="py-4 w-full lg:w-1/2">
<div class="flex flex-col">
<form class="form flex flex-wrap w-full">
<div class="w-full">
<div class="form-element">
<div class="form-label">Current email</div>
<input
name="email"
type="email"
class="form-input"
placeholder="Enter you current email address"
/>
</div>
<div class="form-element">
<div class="form-label">New email</div>
<input
name="email"
type="email"
class="form-input"
placeholder="Enter you new email address"
/>
</div>
<div class="form-element">
<div class="form-label">Daily updates</div>
<div class="flex items-center justify-start space-x-2">
<label class="flex items-center justify-start space-x-2"
><input
type="radio"
name="daily-updates"
class="form-radio h-4 w-4"
value="yes"
/><span class="">Yes</span></label
><label
class="flex items-center justify-start space-x-2"
><input
type="radio"
name="daily-updates"
class="form-radio h-4 w-4"
value="no"
/><span class="">No</span></label
>
</div>
</div>
<div class="form-element">
<div class="form-label">Weekly updates</div>
<div class="flex items-center justify-start space-x-2">
<label class="flex items-center justify-start space-x-2"
><input
type="radio"
name="weekle-updates"
class="form-radio h-4 w-4"
value="yes"
/><span class="">Yes</span></label
><label
class="flex items-center justify-start space-x-2"
><input
type="radio"
name="weekle-updates"
class="form-radio h-4 w-4"
value="no"
/><span class="">No</span></label
>
</div>
</div>
</div>
<input
type="submit"
class="btn btn-default bg-blue-500 hover:bg-blue-600 text-white btn-rounded"
/>
</form>
</div>
</div>
</div>
<div class="tab-content hidden">
<div class="py-4 w-full lg:w-1/2">
<div class="flex flex-col">
<form class="form flex flex-wrap w-full">
<div class="w-full">
<div class="form-element">
<div class="form-label">Current password</div>
<input
name="current-password"
type="password"
class="form-input"
placeholder="Enter your current password"
/>
</div>
<div class="form-element">
<div class="form-label">New password</div>
<input
name="new-password"
type="password"
class="form-input"
placeholder="Enter your new password"
/>
</div>
<div class="form-element">
<div class="form-label">Confirm new password</div>
<input
name="confirm-new-password"
type="password"
class="form-input"
placeholder="Enter your new password confirmation"
/>
</div>
</div>
<input
type="submit"
class="btn btn-default bg-blue-500 hover:bg-blue-600 text-white btn-rounded"
/>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,92 +0,0 @@
<!-- This example requires Tailwind CSS v2.0+ -->
<div class="flex flex-col">
<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="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"
>
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr class="odd:bg-white even:bg-gray-100">
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Name
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Title
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Status
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Role
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_("edit")}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr class="odd:bg-white even:bg-gray-100">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<img
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"
alt=""
/>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
Jane Cooper
</div>
<div class="text-sm text-gray-500">
jane.cooper@example.com
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">
Regional Paradigm Technician
</div>
<div class="text-sm text-gray-500">Optimization</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>
Active
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
Admin
</td>
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a href="#" class="text-indigo-600 hover:text-indigo-900"
>{$_("edit")}</a
>
</td>
</tr>
<!-- More rows... -->
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@@ -1,23 +0,0 @@
<h3 class="text-lg">Tabs</h3>
<div
class="w-full flex sm:border-b sm:border-gray-300 relative flex-col sm:flex-row"
>
<div
class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false"
>
1
</div>
<div
class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false"
>
2
</div>
<div
class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false"
>
3
</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"
/>
</div>

View File

@@ -1,113 +0,0 @@
<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"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-bell-off mr-2"
>
<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="M6.26 6.26A5.86 5.86 0 0 0 6 8c0 7-3 9-3 9h14" />
<path d="M18 8a6 6 0 0 0-9.33-5" />
<line x1="1" y1="1" x2="23" y2="23" />
</svg>
Tag
</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"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-arrow-right mr-2"
>
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
Tag
</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"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-activity mr-2"
>
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
</svg>
Tag
</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"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-archive mr-2"
>
<polyline points="21 8 21 21 3 21 3 8" />
<rect x="1" y="3" width="22" height="5" />
<line x1="10" y1="12" x2="14" y2="12" />
</svg>
Tag
</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"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-hard-drive mr-2"
>
<line x1="22" y1="12" x2="2" y2="12" />
<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"
/>
<line x1="6" y1="16" x2="6.01" y2="16" />
<line x1="10" y1="16" x2="10.01" y2="16" />
</svg>
Tag
</div>
</div>

View File

@@ -12,6 +12,7 @@
import Select from "svelte-select";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
import toast from 'svelte-french-toast'
export let modal_open;
$: selected_team = undefined;
@@ -116,14 +117,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -136,15 +137,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -159,18 +160,18 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-runner")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-required-information-to-add-a-new-runner"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="firstname"
@@ -188,7 +189,7 @@
bind:this={firstname_input}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isFirstnameValid}
<span
@@ -211,7 +212,7 @@
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
@@ -230,7 +231,7 @@
bind:this={lastname_input}
type="text"
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isLastnameValid}
<span
@@ -247,7 +248,7 @@
>{$_("team")}</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"
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-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id
@@ -280,7 +281,7 @@
bind:this={phone_input}
type="tel"
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isPhoneValidOrEmpty}
<span
@@ -308,7 +309,7 @@
bind:this={email_input}
type="email"
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isEmailValidOrEmpty}
<span
@@ -322,13 +323,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create")}
</button>
@@ -337,7 +338,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -31,14 +31,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -51,15 +51,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -74,15 +74,10 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("confirm-delete")}
{$_('delete_runner')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_("please-confirm-the-deletion-of-runner")}
</p>
</div>
<div class="w-full">
<span class="inline-block"
>{delete_runner.firstname} {delete_runner.lastname}</span
@@ -91,11 +86,11 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("delete")}
</button>
@@ -104,7 +99,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -1,401 +1,386 @@
<script>
import csv from "csvtojson";
import { read as readXlsx, utils as xlsx_utils } from "xlsx";
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import csv from "csvtojson";
import { read as readXlsx, utils as xlsx_utils } from "xlsx";
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import {
ImportService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import { createEventDispatcher } from "svelte";
import Select from "svelte-select";
import toast from "svelte-french-toast";
export let opened_from;
export let passed_org;
export let passed_orgs;
export let passed_team;
export let import_modal_open;
$: searchvalue = "";
$: importButtonEnabled =
recent_processed &&
(!(selected_org_or_team == "" || selected_org_or_team == null) ||
!(passed_org?.id == null || passed_org?.id == 0) ||
!(passed_team?.id == null || passed_team?.id == 0));
const dispatch = createEventDispatcher();
function cancelModal() {
json_output = [];
import_modal_open = false;
dispatch("cancel");
}
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
cancelModal();
}
if (e.keyCode === 13) {
//
}
};
})();
let groups = [];
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
const orgs = val.map((r) => {
return { label: r.name, value: `ORG_${r.id}` };
});
groups = groups.concat(orgs);
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
const teams = val.map((r) => {
return {
label: `${r.parentGroup.name} > ${r.name}`,
value: `TEAM_${r.id}`,
};
});
groups = groups.concat(teams);
});
});
let selected_org;
$: selected_org_or_team = "";
let files;
let recent_processed = true;
$: json_output = [];
$: {
if (files) {
if (
files[0].type ===
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
) {
const reader = new FileReader();
reader.addEventListener("load", async (e) => {
const data = new Uint8Array(e.target.result);
const out = readXlsx(data, { type: "array" });
json_output = xlsx_utils.sheet_to_json(
out.Sheets[Object.keys(out.Sheets)[0]]
);
});
reader.readAsArrayBuffer(files[0]);
} else {
const reader = new FileReader();
reader.addEventListener("load", async (e) => {
json_output = await csv({
delimiter: [";", ","],
trim: true,
}).fromString(e.target.result);
});
reader.readAsText(files[0]);
}
}
}
function importAction() {
if (recent_processed === true) {
toast.loading($_("runners-are-being-imported"));
recent_processed = false;
const mapped = json_output.map(function (runner) {
return {
firstname: runner[`${$_("csv_import__firstname")}`],
middlename: runner[`${$_("csv_import__middlename")}`],
lastname: runner[`${$_("csv_import__lastname")}`],
team:
runner[`${$_("csv_import__team")}`] ||
runner[`${$_("csv_import__class")}`],
};
});
let org = 0;
if (opened_from === "OrgDetail") {
org = passed_org.id;
}
if (opened_from === "OrgOverview") {
org = parseInt(selected_org);
}
if (opened_from === "OrgOverview" || opened_from === "OrgDetail") {
ImportService.importControllerPostOrgsJson(org, mapped)
.then((resp) => {
toast.dismiss();
recent_processed = true;
toast.success($_("import-finished"));
cancelModal();
})
.catch((err) => {
toast.dismiss();
recent_processed = true;
toast.error($_("error-during-import"));
cancelModal();
});
}
if (opened_from === "TeamDetail") {
ImportService.importControllerPostTeamsJson(passed_team.id, mapped)
.then((resp) => {
toast.dismiss();
recent_processed = true;
toast.success($_("import-finished"));
cancelModal();
})
.catch((err) => {
toast.dismiss();
recent_processed = true;
toast.error($_("error-during-import"));
cancelModal();
});
}
if (opened_from === "RunnerOverview") {
if (selected_org_or_team.includes("ORG_")) {
selected_org_or_team = selected_org_or_team.split("_")[1];
ImportService.importControllerPostOrgsJson(
selected_org_or_team,
mapped
)
.then((resp) => {
dispatch("created", { runners: resp });
toast.dismiss();
recent_processed = true;
toast($_("import-finished"));
cancelModal();
})
.catch((err) => {
toast.dismiss();
recent_processed = true;
toast.error($_("error-during-import"));
cancelModal();
});
}
if (selected_org_or_team.includes("TEAM_")) {
selected_org_or_team = selected_org_or_team.split("_")[1];
ImportService.importControllerPostTeamsJson(
selected_org_or_team,
mapped
)
.then((resp) => {
dispatch("created", { runners: resp });
toast.dismiss();
recent_processed = true;
toast($_("import-finished"));
cancelModal();
})
.catch((err) => {
toast.dismiss();
recent_processed = true;
toast.error($_("error-during-import"));
cancelModal();
});
}
}
}
}
import {
ImportService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import { createEventDispatcher } from "svelte";
import Select from "svelte-select";
import toast from "svelte-french-toast";
export let opened_from;
export let passed_org;
export let passed_orgs;
export let passed_team;
export let import_modal_open;
$: searchvalue = "";
$: importButtonEnabled =
recent_processed &&
(!(selected_org_or_team == "" || selected_org_or_team == null) ||
!(passed_org?.id == null || passed_org?.id == 0) ||
!(passed_team?.id == null || passed_team?.id == 0));
const dispatch = createEventDispatcher();
function cancelModal() {
json_output = [];
import_modal_open = false;
dispatch("cancel");
}
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
cancelModal();
}
if (e.keyCode === 13) {
//
}
};
})();
let groups = [];
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
const orgs = val.map((r) => {
return { label: r.name, value: `ORG_${r.id}` };
});
groups = groups.concat(orgs);
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
const teams = val.map((r) => {
return {
label: `${r.parentGroup.name} > ${r.name}`,
value: `TEAM_${r.id}`,
};
});
groups = groups.concat(teams);
});
});
let selected_org;
$: selected_org_or_team = "";
let files;
let recent_processed = true;
$: json_output = [];
$: {
if (files) {
if (
files[0].type ===
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
) {
const reader = new FileReader();
reader.addEventListener("load", async (e) => {
const data = new Uint8Array(e.target.result);
const out = readXlsx(data, { type: "array" });
json_output = xlsx_utils.sheet_to_json(
out.Sheets[Object.keys(out.Sheets)[0]]
);
});
reader.readAsArrayBuffer(files[0]);
} else {
const reader = new FileReader();
reader.addEventListener("load", async (e) => {
json_output = await csv({
delimiter: [";", ","],
trim: true,
}).fromString(e.target.result);
});
reader.readAsText(files[0]);
}
}
}
function importAction() {
if (recent_processed === true) {
toast.loading($_("runners-are-being-imported"));
recent_processed = false;
const mapped = json_output.map(function (runner) {
return {
firstname: runner[`${$_("csv_import__firstname")}`],
middlename: runner[`${$_("csv_import__middlename")}`],
lastname: runner[`${$_("csv_import__lastname")}`],
team:
runner[`${$_("csv_import__team")}`] ||
runner[`${$_("csv_import__class")}`],
};
});
let org = 0;
if (opened_from === "OrgDetail") {
org = passed_org.id;
}
if (opened_from === "OrgOverview") {
org = parseInt(selected_org);
}
if (opened_from === "OrgOverview" || opened_from === "OrgDetail") {
ImportService.importControllerPostOrgsJson(org, mapped)
.then((resp) => {
toast.dismiss();
recent_processed = true;
toast.success($_("import-finished"));
cancelModal();
})
.catch((err) => {
toast.dismiss();
recent_processed = true;
toast.error($_("error-during-import"));
cancelModal();
});
}
if (opened_from === "TeamDetail") {
ImportService.importControllerPostTeamsJson(passed_team.id, mapped)
.then((resp) => {
toast.dismiss();
recent_processed = true;
toast.success($_("import-finished"));
cancelModal();
})
.catch((err) => {
toast.dismiss();
recent_processed = true;
toast.error($_("error-during-import"));
cancelModal();
});
}
if (opened_from === "RunnerOverview") {
if (selected_org_or_team.includes("ORG_")) {
selected_org_or_team = selected_org_or_team.split("_")[1];
ImportService.importControllerPostOrgsJson(
selected_org_or_team,
mapped
)
.then((resp) => {
dispatch("created", { runners: resp });
toast.dismiss();
recent_processed = true;
toast.success($_("import-finished"));
cancelModal();
})
.catch((err) => {
toast.dismiss();
recent_processed = true;
toast.error($_("error-during-import"));
cancelModal();
});
}
if (selected_org_or_team.includes("TEAM_")) {
selected_org_or_team = selected_org_or_team.split("_")[1];
ImportService.importControllerPostTeamsJson(
selected_org_or_team,
mapped
)
.then((resp) => {
dispatch("created", { runners: resp });
toast.dismiss();
recent_processed = true;
toast.success($_("import-finished"));
cancelModal();
})
.catch((err) => {
toast.dismiss();
recent_processed = true;
toast.error($_("error-during-import"));
cancelModal();
});
}
}
}
}
</script>
{#if import_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
cancelModal();
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-max sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-2 sm:text-left w-full">
<h3 class="text-lg leading-6 font-bold mt-2 text-gray-900">
{$_("runner-import")}
</h3>
</div>
</div>
<div class="mt-5 text-center sm:mt-0 sm:ml-2 sm:text-left w-full">
{#if json_output.length === 0}
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_("please-provide-the-required-csv-xlsx-file")}
</p>
</div>
<div class="overflow-hidden relative mt-4 mb-4">
<input
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
bind:files
type="file"
/>
</div>
<div class="overflow-hidden relative mt-4 mb-4">
<button
on:click={() => {
cancelModal();
}}
type="button"
class="w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 md:ml-40 mr-0 sm:ml-0 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
{/if}
{#if json_output.length > 0}
{#if opened_from === "OrgOverview"}
<p>{$_("import__target-organization")}</p>
<select
name="team"
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"
>
{#each passed_orgs as o}
<option value={o.id}>{o.name}</option>
{/each}
</select>
<p>{$_("confirm-runner-import")}</p>
{/if}
{#if opened_from === "RunnerOverview"}
<p>{$_("group")}</p>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.id.value
.toString()
.startsWith(filterText.toLowerCase())}
items={groups}
showChevron={true}
placeholder={$_(
"search-for-an-organization-or-team-by-name-or-id"
)}
noOptionsMessage={$_("no-organization-or-team-found")}
on:select={(selectedValue) => {
selected_org_or_team = selectedValue.detail.value;
}}
on:clear={() => (selected_org_or_team = null)}
/>
{/if}
{#if opened_from === "OrgDetail"}
<p>
{$_("runnerimport_verify_runners_org", {
values: { org_name: passed_org.name },
})}
</p>
{/if}
<input
type="search"
bind:value={searchvalue}
placeholder={$_("datatable.search")}
aria-label={$_("datatable.search")}
class="p-2 w-full"
/>
<div class="relative w-full mt-4 mb-4">
<div class="w-full overflow-x-auto">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr class="odd:bg-white even:bg-gray-100">
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("csv_import__firstname")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("csv_import__middlename")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("csv_import__lastname")}
</th>
{#if (opened_from !== "TeamDetail" && opened_from !== "RunnerOverview") || (opened_from === "RunnerOverview" && selected_org_or_team.includes("ORG_"))}
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("csv_import__team")}
</th>
{/if}
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each json_output as runner}
{#if Object.values(runner)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr class="odd:bg-white even:bg-gray-100">
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_("csv_import__firstname")}`]}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_("csv_import__middlename")}`] || ""}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_("csv_import__lastname")}`]}
</td>
{#if (opened_from !== "TeamDetail" && opened_from !== "RunnerOverview") || (opened_from === "RunnerOverview" && selected_org_or_team.includes("ORG_"))}
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_("csv_import__team")}`] ||
runner[`${$_("csv_import__class")}`] ||
"---"}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
<button
disabled={!importButtonEnabled}
class:opacity-50={!importButtonEnabled}
on:click={importAction}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("import-runners")}
</button>
<button
on:click={() => {
cancelModal();
}}
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"
>
{$_("cancel")}
</button>
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
<div
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
cancelModal();
}}
>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div
class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl lg:rounded-xl"
>
<div class="">
<div
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"
/></svg
>
</div>
<div class="mt-3 sm:mt-0 sm:text-left w-full">
<h3 class="text-lg leading-6 font-bold mt-2 text-gray-900">
{$_("runner-import")}
</h3>
</div>
</div>
<div class="sm:text-left w-full">
{#if json_output.length === 0}
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_("please-provide-the-required-csv-xlsx-file")}
</p>
</div>
<div class="overflow-hidden relative mt-4 mb-4">
<input
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
bind:files
type="file"
/>
</div>
<div class="overflow-hidden relative mt-4 mb-4">
<button
on:click={() => {
cancelModal();
}}
type="button"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>
</div>
{/if}
{#if json_output.length > 0}
{#if opened_from === "OrgOverview"}
<p>{$_("import__target-organization")}</p>
<select
name="team"
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-neutral-800 rounded-md p-2"
>
{#each passed_orgs as o}
<option value={o.id}>{o.name}</option>
{/each}
</select>
<p>{$_("confirm-runner-import")}</p>
{/if}
{#if opened_from === "RunnerOverview"}
<p>{$_("group")}</p>
<select
bind:value={selected_org_or_team}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
>
{#each groups as g}
<option value={g.value}>{g.label}</option>
{/each}
</select>
{/if}
{#if opened_from === "OrgDetail"}
<p>
{$_("runnerimport_verify_runners_org", {
values: { org_name: passed_org.name },
})}
</p>
{/if}
<div class="relative w-full mt-4 mb-4">
<div class="w-full overflow-x-auto max-h-[50vh]">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr class="odd:bg-white even:bg-gray-100">
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("csv_import__firstname")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("csv_import__middlename")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("csv_import__lastname")}
</th>
{#if (opened_from !== "TeamDetail" && opened_from !== "RunnerOverview") || (opened_from === "RunnerOverview" && selected_org_or_team.includes("ORG_"))}
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("csv_import__team")}
</th>
{/if}
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each json_output as runner}
{#if Object.values(runner)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr class="odd:bg-white even:bg-gray-100">
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_("csv_import__firstname")}`]}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_("csv_import__middlename")}`] || ""}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_("csv_import__lastname")}`]}
</td>
{#if (opened_from !== "TeamDetail" && opened_from !== "RunnerOverview") || (opened_from === "RunnerOverview" && selected_org_or_team.includes("ORG_"))}
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_("csv_import__team")}`] ||
runner[`${$_("csv_import__class")}`] ||
"---"}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
<button
disabled={!importButtonEnabled}
class:opacity-50={!importButtonEnabled}
on:click={importAction}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("import-runners")}
</button>
<button
on:click={() => {
cancelModal();
}}
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"
>
{$_("cancel")}
</button>
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
{/if}

View File

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

View File

@@ -1,60 +1,337 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddRunnerModal from "./AddRunnerModal.svelte";
import ImportRunnerModal from "./ImportRunnerModal.svelte";
import RunnersOverview from "./RunnersOverview.svelte";
$: current_runners = [];
export let modal_open = false;
export let import_modal_open = false;
let addRunners;
import {
RunnerOrganizationService,
RunnerService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { onMount } from "svelte";
import { writable } from "svelte/store";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import InputElement from "../shared/InputElement.svelte";
import TableActions from "../shared/TableActions.svelte";
import { groupFilter } from "../shared/tablefilters";
import DeleteRunnerModal from "./DeleteRunnerModal.svelte";
import TableBottom from "../shared/TableBottom.svelte";
import TableHeader from "../shared/TableHeader.svelte";
$: selectedRunners =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: active_delete = undefined;
let dataLoaded = false;
export let created_via = "all";
export let current_runners = [];
$: sponsoring_contracts_show = selected.length > 0;
$: cards_show = selected.length > 0;
$: certificates_show = selected.length > 0;
$: teams = [];
$: orgs = [];
export const addRunners = (runners) => {
current_runners = current_runners.concat(...runners);
options.update((options) => ({
...options,
data: current_runners,
}));
};
//Section table
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "firstname",
header: () => $_("first-name"),
filterFn: `includesString`,
},
{
accessorKey: "middlename",
header: () => $_("middle-name"),
cell: (info) => {
if (!info || !info.getValue()) {
return "";
}
return info.getValue();
},
filterFn: `includesString`,
},
{
accessorKey: "lastname",
header: () => $_("last-name"),
filterFn: `includesString`,
},
{
accessorKey: "created_via",
header: () => "created_via",
filterFn: `includesString`,
},
{
accessorKey: "group",
header: () => $_("group"),
cell: (info) => {
const group = info.getValue();
if (group.responseType === "RUNNERORGANIZATION") {
return group.name;
}
return `${group.parentGroup.name} > ${group.name}`;
},
filterFn: `group`,
sortingFn: (rowA, rowB, col) => {
return rowA.original.group.name.localeCompare(rowB.original.group.name);
},
},
{
accessorKey: "distance",
header: () => $_("distance"),
sortingFn: (rowA, rowB, col) => {
return rowA.original.distance > rowB.original.distance;
},
cell: (info) => {
if (info.getValue() < 1000) {
return `${info.getValue()} m`;
}
return `${(info.getValue() / 1000).toFixed(1)} km`;
},
enableColumnFilter: false,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsLink: `/runners/${info.row.original.id}`,
deleteAction: () => {
active_delete =
current_runners[
current_runners.findIndex((r) => r.id == info.row.original.id)
];
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes(
"RUNNER:DELETE"
),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
filterFns: {
group: groupFilter,
},
initialState: {
pagination: {
pageSize: 50,
},
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
async function deleteRunner(delete_runner_id) {
await RunnerService.runnerControllerRemove(delete_runner_id, true);
current_runners = current_runners.filter((r) => r.id !== delete_runner_id);
options.update((options) => ({
...options,
data: current_runners,
}));
toast.success($_("runner-deleted"));
}
onMount(async () => {
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
orgs = val;
}
);
let page = 0;
while (page >= 0) {
const runners = await RunnerService.runnerControllerGetAll(
page,
500,
created_via
);
if (runners.length == 0) {
page = -2;
}
current_runners = current_runners.concat(...runners);
options.update((options) => ({
...options,
data: current_runners,
}));
dataLoaded = true;
page++;
}
});
import { _ } from "svelte-i18n";
import store from "../../store";
import AddRunnerModal from "./AddRunnerModal.svelte";
import ImportRunnerModal from "./ImportRunnerModal.svelte";
import toast from "svelte-french-toast";
$: current_runners = [];
export let modal_open = false;
export let import_modal_open = false;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_("runners")}
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("laeufer-hinzufuegen")}
</button>
<button
on:click={() => {
import_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("import-runners")}
</button>
{/if}
</span>
<RunnersOverview bind:current_runners bind:addRunners />
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("runners")}
</h4>
{#if created_via !== "all"}
<p>created_via={created_via}</p>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>
{$_("laeufer-hinzufuegen")}
</button>
<button
on:click={() => {
import_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>
{$_("import-runners")}
</button>
{/if}
<DeleteRunnerModal
delete_runner={active_delete}
modal_open={active_delete != undefined}
on:delete={(event) => {
deleteRunner(event.detail.id);
}}
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")}
{#if !dataLoaded}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("runners-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:else}
<GenerateSponsoringContracts
bind:sponsoring_contracts_show
bind:generate_runners={selectedRunners}
/>
<GenerateRunnerCards
bind:cards_show
bind:generate_runners={selectedRunners}
/>
<GenerateRunnerCertificates
bind:certificates_show
bind:generate_runners={selectedRunners}
/>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="border-b border-gray-400">
{#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<InputElement
type="checkbox"
checked={$table.getIsAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()}
/>
</th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr>
{/each}
</thead>
<tbody>
{#each $table.getRowModel().rows as row}
<tr class="odd:bg-white even:bg-gray-100">
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement
type="checkbox"
checked={row.getIsSelected()}
on:change={() => row.toggleSelected()}
/>
</td>
{#each row.getVisibleCells() as cell}
<td>
<svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
<div class="h-2" />
{/if}
{/if}
<TableBottom {table} {selected} />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
<AddRunnerModal
bind:modal_open
on:created={(event) => {
addRunners(event.detail.runners);
}}
/>
<ImportRunnerModal
on:cancelDelete={(event) => {
import_modal_open = false;
}}
passed_team={{}}
passed_orgs={[]}
passed_org={{}}
opened_from="RunnerOverview"
bind:import_modal_open
on:created={(event) => {
addRunners(event.detail.runners);
}}
/>
<AddRunnerModal
bind:modal_open
on:created={(event) => {
addRunners(event.detail.runners);
}}
/>
<ImportRunnerModal
on:cancelDelete={(event) => {
import_modal_open = false;
}}
passed_team={{}}
passed_orgs={[]}
passed_org={{}}
opened_from="RunnerOverview"
bind:import_modal_open
on:created={(event) => {
addRunners(event.detail.runners);
}}
/>
{/if}
<style>
table tbody tr td:nth-child(2) {
font-family: monospace;
}
</style>

View File

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

View File

@@ -68,14 +68,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -88,15 +88,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -112,18 +112,18 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-scan-fixed-only")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-nessecary-information-to-create-a-new-scan"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="donor"
@@ -131,7 +131,7 @@
>{$_("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"
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-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)}
items={runners}
@@ -160,7 +160,7 @@
type="number"
step="1"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
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-neutral-800 p-2"
placeholder="400"
/>
<span
@@ -180,13 +180,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create")}
</button>
@@ -195,7 +195,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -33,14 +33,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -53,15 +53,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -76,21 +76,21 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("confirm-delete")}
{$_("please-confirm-the-deletion-of-scan")}
</h3>
<div class="mt-2 mb-6">
{$_("please-confirm-the-deletion-of-scan")} #{delete_scan.id}
<div class="mb-6">
#{delete_scan.id}
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("delete")}
</button>
@@ -99,7 +99,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -1,288 +1,273 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import { RunnerService, ScanService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import store from "../../store";
import { RunnerService, ScanService } from "@odit/lfk-client-js";
import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select";
let data_loaded = false;
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: current_runners = [];
$: is_distance_valid = editable.distance > 0;
$: is_everything_set =
editable.runner != null &&
((original_data.responseType === "TRACKSCAN" && editable.track != null) ||
original_data.responseType !== "TRACKSCAN");
$: runner = {};
$: changes_performed = !(
JSON.stringify(original_data) === JSON.stringify(editable)
);
$: save_enabled = changes_performed && is_everything_set && is_distance_valid;
import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select";
let data_loaded = false;
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: current_runners = [];
$: is_distance_valid = editable.distance > 0;
$: is_everything_set =
editable.runner != null &&
((original_data.responseType === "TRACKSCAN" && editable.track != null) ||
original_data.responseType !== "TRACKSCAN");
$: runner = {};
$: changes_performed = !(
JSON.stringify(original_data) === JSON.stringify(editable)
);
$: save_enabled = changes_performed && is_everything_set && is_distance_valid;
const promise = ScanService.scanControllerGetOne(params.scanid).then(
(data) => {
data_loaded = true;
original_data = Object.assign(original_data, data);
original_data.runner = original_data.runner.id;
editable = Object.assign(editable, original_data);
RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r };
});
runner = current_runners.find((r) => r.value.id == editable.runner);
});
}
);
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase());
const promise = ScanService.scanControllerGetOne(params.scanid).then(
(data) => {
data_loaded = true;
original_data = Object.assign(original_data, data);
original_data.runner = original_data.runner.id;
editable = Object.assign(editable, original_data);
RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r };
});
runner = current_runners.find((r) => r.value.id == editable.runner);
});
}
);
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase());
function submit() {
if (data_loaded === true && save_enabled) {
toast($_("scan-is-being-updated"));
let postdata = {};
if (original_data.responseType === "TRACKSCAN") {
postdata = Object.assign(postdata, editable);
postdata.track = postdata.track.id;
ScanService.scanControllerPutTrackScan(original_data.id, postdata)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("updated-scan"));
})
.catch((err) => {});
} else {
postdata = Object.assign(postdata, editable);
ScanService.scanControllerPut(original_data.id, postdata)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("updated-scan"));
})
.catch((err) => {});
}
} else {
}
}
function deleteScan() {
ScanService.scanControllerRemove(original_data.id, false)
.then((resp) => {
toast($_("deleted-scan"));
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_scan = original_data;
});
}
function format_laptime(laptime) {
if (laptime == 0 || laptime == null) {
return $_("first-scan-of-the-day");
}
if (laptime < 60) {
return `${laptime}s`;
}
if (laptime < 3600) {
return `${Math.floor(laptime / 60)}min ${
laptime - Math.floor(laptime / 60) * 60
}s`;
}
return `${Math.floor(laptime / 3600)}h ${
laptime - Math.floor(laptime / 3600) * 3600
}min ${
laptime -
Math.floor(laptime / 3600) * 3600 -
Math.floor(laptime / 60) * 60
}`;
}
function submit() {
if (data_loaded === true && save_enabled) {
toast($_("scan-is-being-updated"));
let postdata = {};
if (original_data.responseType === "TRACKSCAN") {
postdata = Object.assign(postdata, editable);
postdata.track = postdata.track.id;
ScanService.scanControllerPutTrackScan(original_data.id, postdata)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("updated-scan"));
})
.catch((err) => {});
} else {
postdata = Object.assign(postdata, editable);
ScanService.scanControllerPut(original_data.id, postdata)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("updated-scan"));
})
.catch((err) => {});
}
} else {
}
}
function deleteScan() {
ScanService.scanControllerRemove(original_data.id, false)
.then((resp) => {
toast.success($_("deleted-scan"));
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_scan = original_data;
});
}
function format_laptime(laptime) {
if (laptime == 0 || laptime == null) {
return $_("first-scan-of-the-day");
}
if (laptime < 60) {
return `${laptime}s`;
}
if (laptime < 3600) {
return `${Math.floor(laptime / 60)}min ${
laptime - Math.floor(laptime / 60) * 60
}s`;
}
return `${Math.floor(laptime / 3600)}h ${
laptime - Math.floor(laptime / 3600) * 3600
}min ${
laptime -
Math.floor(laptime / 3600) * 3600 -
Math.floor(laptime / 60) * 60
}`;
}
</script>
{#await promise}
Loading scan details
Loading scan details
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path
fill="currentColor"
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z"
/></svg
>
</li>
<li class="flex items-center ml-2">
<a class="mr-2" href="./">Scans</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
</li>
<li class="flex items-center">
<span class="mr-2">{original_data.id}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
{runner.value?.firstname}
{runner.value?.middlename || ""}
{runner.value?.lastname}
#{original_data.id}
<span data-id="donation_actions_${original_data.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:DELETE")}
{#if delete_triggered}
<button
on:click={deleteScan}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
>{$_("delete-scan")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:"
>{$_("save-changes")}</button
>
{/if}
</span>
</div>
<!-- -->
<div class="w-full inline-flex">
<label for="valid" class="block font-medium text-gray-700"
>{$_("status")}:
</label>
&nbsp;
<input
id="valid"
on:change={() => {
editable.valid = !editable.valid;
}}
name="valid"
type="checkbox"
checked={editable.valid}
class="focus:ring-indigo-500 align-bottom h-7 w-5font-medium text-indigo-600 border-gray-300 rounded"
/>
&nbsp;
<p class="font-medium">
{#if editable.valid}{$_("valid")}{:else}{$_("invalid")}{/if}
</p>
</div>
{#if editable.responseType === "TRACKSCAN"}
<div class="w-full inline-flex">
<label for="valid" class="block font-semibold text-gray-700"
>{$_("track")}:
</label>
<a
href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{editable.track.name}
</a>
</div>
<div class="w-full inline-flex pb-3">
<label for="valid" class="block font-semibold text-gray-700"
>{$_("laptime")}: {format_laptime(editable.laptime)}
</label>
</div>
{/if}
<div class=" w-full">
<label for="runner" class="block font-medium text-gray-700"
>{$_("runner")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)}
items={current_runners}
showChevron={true}
isDisabled={editable.responseType === "TRACKSCAN"}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
bind:selectedValue={runner}
on:select={(selectedValue) => {
editable.runner = selectedValue.detail.value.id;
}}
on:clear={() => (editable.runner = null)}
/>
</div>
<div class=" w-full">
<label
for="scan_distance"
class="block text-sm font-medium text-gray-700"
>
{$_("distance")}</label
>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_distance_valid}
class:focus:border-red-500={!is_distance_valid}
class:focus:ring-red-500={!is_distance_valid}
bind:value={editable.distance}
disabled={editable.responseType === "TRACKSCAN"}
type="number"
step="1"
name="scan_distance"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="400"
/>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
>m</span
>
</div>
{#if !is_distance_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("the-scans-distance-must-be-greater-than-0m")}
</span>
{/if}
</div>
</section>
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<a class="mr-2" href="./"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="inline-block"
><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg
> Scans</a
>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-4 text-3xl font-extrabold leading-tight">
{runner.value?.firstname}
{runner.value?.middlename || ""}
{runner.value?.lastname}
- Scan #{original_data.id}
<div data-id="donation_actions_${original_data.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:DELETE")}
{#if delete_triggered}
<button
on:click={deleteScan}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
>{$_("delete-scan")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>{$_("save-changes")}</button
>
{/if}
</div>
</div>
<!-- -->
<div class="w-full inline-flex">
<label for="valid" class="block font-medium text-gray-700"
>{$_("status")}:
</label>
&nbsp;
<input
id="valid"
on:change={() => {
editable.valid = !editable.valid;
}}
name="valid"
type="checkbox"
checked={editable.valid}
class="focus:ring-indigo-500 align-bottom h-7 w-5font-medium text-indigo-600 border-gray-300 rounded"
/>
&nbsp;
<p class="font-medium">
{#if editable.valid}{$_("valid")}{:else}{$_("invalid")}{/if}
</p>
</div>
{#if editable.responseType === "TRACKSCAN"}
<div class="w-full inline-flex">
<label for="valid" class="block font-semibold text-gray-700"
>{$_("track")}:
</label>
<a
href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 border border-current"
>{editable.track.name}
</a>
</div>
<div class="w-full inline-flex pb-3">
<label for="valid" class="block font-semibold text-gray-700"
>{$_("laptime")}: {format_laptime(editable.laptime)}
</label>
</div>
{/if}
<div class=" w-full">
<label for="runner" class="block font-medium text-gray-700"
>{$_("runner")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)}
items={current_runners}
showChevron={true}
isDisabled={editable.responseType === "TRACKSCAN"}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
bind:selectedValue={runner}
on:select={(selectedValue) => {
editable.runner = selectedValue.detail.value.id;
}}
on:clear={() => (editable.runner = null)}
/>
</div>
<div class=" w-full">
<label
for="scan_distance"
class="block text-sm font-medium text-gray-700"
>
{$_("distance")}</label
>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_distance_valid}
class:focus:border-red-500={!is_distance_valid}
class:focus:ring-red-500={!is_distance_valid}
bind:value={editable.distance}
disabled={editable.responseType === "TRACKSCAN"}
type="number"
step="1"
name="scan_distance"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 p-2"
placeholder="400"
/>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
>m</span
>
</div>
{#if !is_distance_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("the-scans-distance-must-be-greater-than-0m")}
</span>
{/if}
</div>
</section>
{:catch error}
<PromiseError {error} />
<PromiseError {error} />
{/await}

View File

@@ -5,12 +5,12 @@
{#if valid}
<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 border border-current bg-green-100 text-green-800"
>{$_("valid")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-red-100 text-red-800"
>{$_("invalid")}</span
>
{/if}

View File

@@ -9,20 +9,20 @@
</script>
<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">
{$_("scans")}
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("add-scan")}
</button>
{/if}
</span>
</h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm"
>
{$_("add-scan")}
</button>
{/if}
<ScansOverview bind:current_scans bind:addScans />
</section>

View File

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

View File

@@ -174,7 +174,7 @@
...options,
data: current_scans,
}));
toast($_("scan-deleted"));
toast.success($_("scan-deleted"));
}
onMount(async () => {
@@ -220,7 +220,7 @@
{#if selected.length > 0}
<button
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
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"
id="options-menu"
on:click={async () => {
const prom = [];

View File

@@ -72,14 +72,14 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -92,15 +92,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -115,25 +115,25 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:mt-0">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-scanstation")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-required-information-to-create-a-new-scanstation"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="track"
class="block text-sm font-medium text-gray-700">Track</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
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-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id
@@ -161,11 +161,11 @@
bind:value={description}
type="text"
name="description"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
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-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label for="enabled" class="font-medium text-gray-700"
<label for="enabled" class="font-semibold text-gray-700"
>{$_("enabled_large")}</label
>
<br />
@@ -188,13 +188,13 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("create")}
</button>
@@ -203,7 +203,7 @@
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"
class="w-full 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 hidden lg:block"
>
{$_("cancel")}
</button>

View File

@@ -15,7 +15,7 @@
function deleteStation() {
ScanStationService.scanStationControllerRemove(delete_station.id, true)
.then((resp) => {
toast($_("station-deleted"));
toast.success($_("station-deleted"));
location.replace("./");
})
.catch((err) => {});
@@ -24,12 +24,12 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={cancelDelete}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -42,15 +42,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -62,11 +62,11 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("attention")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"do-you-want-to-delete-this-donor-with-all-related-donations"
@@ -78,18 +78,18 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
on:click={deleteStation}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("confirm-delete-station-with-all-scans")}
</button>
<button
on:click={cancelDelete}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
class="w-full 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 hidden lg:block"
>
{$_("cancel-keep-station")}
</button>

View File

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

View File

@@ -1,204 +1,190 @@
<script>
import { t, _ } from "svelte-i18n";
import store from "../../store";
import { ScanStationService, TrackService } from "@odit/lfk-client-js";
import { t, _ } from "svelte-i18n";
import store from "../../store";
import { ScanStationService, TrackService } from "@odit/lfk-client-js";
import PromiseError from "../base/PromiseError.svelte";
import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte";
import Select from "svelte-select";
let data_loaded = false;
let modal_open;
let delete_station;
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: tracks = [];
$: track = {};
$: changes_performed = !(
JSON.stringify(original_data) === JSON.stringify(editable)
);
$: save_enabled = changes_performed;
const promise = ScanStationService.scanStationControllerGetOne(
params.stationid
).then((data) => {
data_loaded = true;
data.track = data.track.id;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
TrackService.trackControllerGetAll().then((val) => {
tracks = val.map((t) => {
return { label: t.name || `#{t.id}`, value: t };
});
track = tracks.find((t) => t.value.id == editable.track);
});
});
function submit() {
if (data_loaded === true && save_enabled) {
toast($_("station-is-being-updated"));
ScanStationService.scanStationControllerPut(original_data.id, editable)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("updated-station"));
})
.catch((err) => {});
} else {
}
}
function deleteStation() {
ScanStationService.scanStationControllerRemove(original_data.id, false)
.then((resp) => {
toast($_("station-deleted"));
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_station = original_data;
});
}
import PromiseError from "../base/PromiseError.svelte";
import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte";
import Select from "svelte-select";
let data_loaded = false;
let modal_open;
let delete_station;
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: tracks = [];
$: track = {};
$: changes_performed = !(
JSON.stringify(original_data) === JSON.stringify(editable)
);
$: save_enabled = changes_performed;
const promise = ScanStationService.scanStationControllerGetOne(
params.stationid
).then((data) => {
data_loaded = true;
data.track = data.track.id;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
TrackService.trackControllerGetAll().then((val) => {
tracks = val.map((t) => {
return { label: t.name || `#{t.id}`, value: t };
});
track = tracks.find((t) => t.value.id == editable.track);
});
});
function submit() {
if (data_loaded === true && save_enabled) {
toast($_("station-is-being-updated"));
ScanStationService.scanStationControllerPut(original_data.id, editable)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
toast.success($_("updated-station"));
})
.catch((err) => {});
} else {
}
}
function deleteStation() {
ScanStationService.scanStationControllerRemove(original_data.id, false)
.then((resp) => {
toast.success($_("station-deleted"));
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_station = original_data;
});
}
</script>
<ConfirmScanStationDeletion bind:modal_open bind:delete_station />
{#await promise}
{$_("loading-station-details")}
{$_("loading-station-details")}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
/></svg
>
</li>
<li class="flex items-center ml-2">
<a class="mr-2" href="./">{$_("scanstation")}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
</li>
<li class="flex items-center">
<span class="mr-2">#{original_data.id}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
#{original_data.id}
<span data-id="stations_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:DELETE")}
{#if delete_triggered}
<button
on:click={deleteStation}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("delete-station")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("save-changes")}</button
>
{/if}
</span>
</div>
<!-- -->
<div class="text-sm w-full">
<label for="track" class="block text-sm font-medium text-gray-700"
>Track</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase())}
items={tracks}
showChevron={true}
placeholder="Search for a track (by name or id)."
noOptionsMessage="No track found"
bind:selectedValue={track}
on:select={(selectedValue) =>
(editable.track = selectedValue.detail.value.id)}
on:clear={() => (track = null)}
/>
</div>
<div class="text-sm w-full">
<label for="description" class="font-medium text-gray-700"
>{$_("description")}</label
>
<input
autocomplete="off"
placeholder={$_("description")}
type="text"
bind:value={editable.description}
name="description"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div>
<div class="text-sm w-full">
<label for="enabled" class="ml-1 font-medium text-gray-700"
>{$_("enabled")}</label
>
<br />
<p class="text-gray-500">
<input
id="enabled"
on:change={() => {
editable.enabled = !editable.enabled;
}}
name="enabled"
type="checkbox"
checked={editable.enabled}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
{$_("this-scanstation-is")}
{#if editable.enabled}{$_("enabled")}{:else}{$_("disabled")}{/if}
</p>
</div>
</section>
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<a class="mr-2" href="./"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="inline-block"
><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg
>
{$_("scanstations")}</a
>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-4 text-3xl font-extrabold leading-tight">
{$_("scanstation")} #{original_data.id}<br>"{original_data.description}"
<div data-id="stations_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:DELETE")}
{#if delete_triggered}
<button
on:click={deleteStation}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
>{$_("confirm-deletion")}</button
>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm"
>{$_("cancel")}</button
>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
>{$_("delete-station")}</button
>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
>{$_("save-changes")}</button
>
{/if}
</div>
</div>
<!-- -->
<div class="mt-2 text-sm w-full">
<label for="track" class="block text-sm font-semibold text-gray-700"
>Track</label
>
<Select
containerClasses="rounded-l-md focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase())}
items={tracks}
showChevron={true}
placeholder="Search for a track (by name or id)."
noOptionsMessage="No track found"
bind:selectedValue={track}
on:select={(selectedValue) =>
(editable.track = selectedValue.detail.value.id)}
on:clear={() => (track = null)}
/>
</div>
<div class="mt-2 text-sm w-full">
<label for="description" class="font-semibold text-gray-700"
>{$_("description")}</label
>
<input
autocomplete="off"
placeholder={$_("description")}
type="text"
bind:value={editable.description}
name="description"
class="focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="mt-2 text-sm w-full">
<label for="enabled" class="font-semibold text-gray-700"
>{$_("enabled")}</label
>
<br />
<p class="text-gray-500">
<input
id="enabled"
on:change={() => {
editable.enabled = !editable.enabled;
}}
name="enabled"
type="checkbox"
checked={editable.enabled}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
{$_("this-scanstation-is")}
{#if editable.enabled}{$_("enabled")}{:else}{$_("disabled")}{/if}
</p>
</div>
</section>
{:catch error}
<PromiseError {error} />
<PromiseError {error} />
{/await}

View File

@@ -1,44 +1,229 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddScanStationModal from "./AddScanStationModal.svelte";
import CopyScanStationTokenModal from "./CopyScanStationTokenModal.svelte";
import ScanStationsOverview from "./ScanStationsOverview.svelte";
export let modal_open = false;
export let copy_modal_open = false;
export let new_station = {};
let current_stations = [];
import { _ } from "svelte-i18n";
import store from "../../store";
import { ScanStationService } from "@odit/lfk-client-js";
import AddScanStationModal from "./AddScanStationModal.svelte";
import CopyScanStationTokenModal from "./CopyScanStationTokenModal.svelte";
import ScanStationsEmptyState from "./ScanStationsEmptyState.svelte";
import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte";
import toast from "svelte-french-toast";
//
export let modal_open = false;
export let copy_modal_open = false;
export let new_station = {};
//
const promise = ScanStationService.scanStationControllerGetAll().then(
(result) => {
current_stations = result;
}
);
$: searchvalue = "";
$: active_deletes = [];
let delete_station = {};
let current_stations = [];
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_("scanstations")}
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("create-a-new-scanstation")}
</button>
{/if}
</span>
<ScanStationsOverview
bind:current_stations
bind:modal_open
bind:new_station
bind:copy_modal_open
/>
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("scanstations")}
</h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm"
>
{$_("create-a-new-scanstation")}
</button>
{/if}
<ConfirmScanStationDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open
bind:delete_station
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")}
{#await promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("scanstations-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then}
{#if current_stations.length === 0}
<ScanStationsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_("datatable.search")}
aria-label={$_("datatable.search")}
class="mb-2 w-full sm:w-auto mt-1 sm:mt-0 p-2 rounded-md border"
/>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr class="odd:bg-white even:bg-gray-100">
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("track")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("description")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("status")}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_("action")}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_stations as s}
{#if Object.values(s)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr
class="odd:bg-white even:bg-gray-100"
data-rowid="station_{s.id}"
>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
<a
href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 border border-current"
>
{s.track.name || s.track.distance + "m"}</a
>
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{s.description}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if s.enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-green-100 text-green-800"
>{$_("active")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-red-100 text-red-800"
>{$_("inactive")}</span
>
{/if}
</div>
</td>
{#if active_deletes[s.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button
on:click={() => {
active_deletes[s.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button
on:click={() => {
ScanStationService.scanStationControllerRemove(
s.id,
false
)
.then((resp) => {
current_stations = current_stations.filter(
(obj) => obj.id !== s.id
);
toast.success($_("station-deleted"));
})
.catch((err) => {
modal_open = true;
delete_station = s;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a
href="/scanstations/{s.id}"
class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:DELETE")}
<button
on:click={() => {
active_deletes[s.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"
>
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_("general_promise_error")}</b>
{error}
</span>
</div>
{/await}
{/if}
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:CREATE")}
<AddScanStationModal
bind:modal_open
bind:current_stations
bind:new_station
bind:copy_modal_open
/>
<CopyScanStationTokenModal bind:copy_modal_open bind:new_station />
<AddScanStationModal
bind:modal_open
bind:current_stations
bind:new_station
bind:copy_modal_open
/>
<CopyScanStationTokenModal bind:copy_modal_open bind:new_station />
{/if}

View File

@@ -1,195 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import { ScanStationService } from "@odit/lfk-client-js";
const promise = ScanStationService.scanStationControllerGetAll().then(
(result) => {
current_stations = result;
}
);
import store from "../../store";
import ScanStationsEmptyState from "./ScanStationsEmptyState.svelte";
import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte";
import toast from "svelte-french-toast";
$: searchvalue = "";
$: active_deletes = [];
let delete_station = {};
let modal_open = false;
export let current_stations = [];
</script>
<ConfirmScanStationDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open
bind:delete_station
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")}
{#await promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("scanstations-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then}
{#if current_stations.length === 0}
<ScanStationsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_("datatable.search")}
aria-label={$_("datatable.search")}
class="mb-4"
/>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr class="odd:bg-white even:bg-gray-100">
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("track")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("description")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("status")}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_("action")}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_stations as s}
{#if Object.values(s)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr
class="odd:bg-white even:bg-gray-100"
data-rowid="station_{s.id}"
>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
<a
href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>
{s.track.name || s.track.distance + "m"}</a
>
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{s.description}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if s.enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("active")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("inactive")}</span
>
{/if}
</div>
</td>
{#if active_deletes[s.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button
on:click={() => {
active_deletes[s.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button
on:click={() => {
ScanStationService.scanStationControllerRemove(
s.id,
false
)
.then((resp) => {
current_stations = current_stations.filter(
(obj) => obj.id !== s.id
);
toast($_("station-deleted"));
})
.catch((err) => {
modal_open = true;
delete_station = s;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a
href="/scanstations/{s.id}"
class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:DELETE")}
<button
on:click={() => {
active_deletes[s.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_("general_promise_error")}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@@ -25,12 +25,12 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={cancelDelete}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
@@ -43,15 +43,15 @@
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
@@ -63,11 +63,11 @@
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("attention")}
</h3>
<div class="mt-2 mb-6">
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_("do-you-really-want-to-delete-your-profile")}
<br />
@@ -81,18 +81,18 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
on:click={deleteMe}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
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"
>
{$_("confirm-delete-my-user-profile")}
</button>
<button
on:click={cancelDelete}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
class="w-full 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 hidden lg:block"
>
{$_("cancel-keep-my-profile")}
</button>

View File

@@ -2,6 +2,7 @@
import { _ } from "svelte-i18n";
import isEmail from "validator/es/lib/isEmail";
import { MeService } from "@odit/lfk-client-js";
import toast from 'svelte-french-toast'
import ConfirmProfileDeletion from "./ConfirmProfileDeletion.svelte";
import PasswordStrength, {
@@ -61,21 +62,13 @@
</script>
<ConfirmProfileDeletion bind:modal_open bind:delete_triggered />
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8">
<h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"
>
🔨<br />{$_("settings")}
</h1>
</div>
</div>
<div class="pt-0 pb-16 bg-gray-50 overflow-hidden lg:pt-12 lg:py-24">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<span class="text-3xl font-bold">{$_("settings")}</span>
<div>
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:grid md:grid-cols-3 md:gap-2 lg:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<div class="sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">
{$_("profile")}
</h3>
@@ -90,8 +83,8 @@
<div class="mt-5 md:mt-0 md:col-span-2">
<div class="shadow sm:rounded-md sm:overflow-hidden">
<div class="px-4 py-5 bg-white space-y-6 sm:p-6">
<div class="text-sm w-full">
<label for="username" class="font-medium text-gray-700"
<div class="text-sm w-full mt-2">
<label for="username" class="font-semibold text-gray-700"
>{$_("username")}</label
>
<input
@@ -100,11 +93,11 @@
type="text"
bind:value={editable.username}
name="username"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 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-neutral-800 rounded-md p-2"
/>
</div>
<div class="text-sm w-full">
<label for="email" class="font-medium text-gray-700"
<div class="text-sm w-full mt-2">
<label for="email" class="font-semibold text-gray-700"
>{$_("e-mail-adress")}</label
>
<input
@@ -113,7 +106,7 @@
type="email"
bind:value={editable.email}
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 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-neutral-800 rounded-md p-2"
/>
</div>
{#if !isEmail(editable.email)}
@@ -122,8 +115,8 @@
>{$_("valid-email-is-required")}</span
>
{/if}
<div class="text-sm w-full">
<label for="firstname" class="font-medium text-gray-700"
<div class="text-sm w-full mt-2">
<label for="firstname" class="font-semibold text-gray-700"
>{$_("first-name")}</label
>
<input
@@ -132,11 +125,11 @@
type="text"
bind:value={editable.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 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-neutral-800 rounded-md p-2"
/>
</div>
<!-- <div class="text-sm w-full">
<label for="middlename" class="font-medium text-gray-700"
<!-- <div class="text-sm w-full mt-2">
<label for="middlename" class="font-semibold text-gray-700"
>{$_("middle-name")}</label
>
<input
@@ -145,11 +138,11 @@
type="text"
bind:value={editable.middlename}
name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
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-neutral-800 rounded-md p-2"
/>
</div> -->
<div class="text-sm w-full">
<label for="lastname" class="font-medium text-gray-700"
<div class="text-sm w-full mt-2">
<label for="lastname" class="font-semibold text-gray-700"
>{$_("last-name")}</label
>
<input
@@ -158,7 +151,7 @@
type="text"
bind:value={editable.lastname}
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 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-neutral-800 rounded-md p-2"
/>
</div>
</div>
@@ -168,7 +161,7 @@
disabled={!save_enabled}
class:opacity-50={!save_enabled}
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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 mb-1 lg:mb-0"
>
{$_("save-changes")}
</button>
@@ -181,9 +174,9 @@
</div>
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<div>
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:grid md:grid-cols-3 md:gap-2 lg:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<div class="sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">
{$_("password")}
</h3>
@@ -198,7 +191,7 @@
<div class="mt-5 md:mt-0 md:col-span-2">
<div class="shadow sm:rounded-md sm:overflow-hidden">
<div class="px-4 py-3 bg-gray-50 text-left sm:px-6">
<label for="new_password" class="font-medium text-gray-700"
<label for="new_password" class="font-semibold text-gray-700"
>{$_("new-password")}</label
>
<div class="-mt-px relative">
@@ -211,7 +204,7 @@
placeholder={$_("password")}
/>
</div>
<label for="new_password" class="font-medium text-gray-700"
<label for="new_password" class="font-semibold text-gray-700"
>{$_("confirm-the-new-password")}</label
>
<div class="-mt-px relative">
@@ -232,7 +225,7 @@
disabled={!update_password_enabled}
class:opacity-50={!update_password_enabled}
on:click={changePassword}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
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 mb-1 lg:mb-0"
>
{$_("update-password")}
</button>
@@ -252,9 +245,9 @@
</div>
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<div>
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:grid md:grid-cols-3 md:gap-2 lg:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<div class="sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">
{$_("danger-zone")}
</h3>
@@ -275,7 +268,7 @@
on:click={() => {
modal_open = true;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
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"
>{$_("confirm-deletion")}</button
>
<button
@@ -292,7 +285,7 @@
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:"
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"
>{$_("delete-profile")}</button
>
{/if}

View File

@@ -1,26 +1,26 @@
<script>
import { _ } from "svelte-i18n";
import { _ } from "svelte-i18n";
export let detailsLink;
export let detailsAction;
export let deleteEnabled;
export let deleteAction;
export let detailsLink = null;
export let detailsAction = null;
export let deleteEnabled;
export let deleteAction;
</script>
{#if detailsLink}
<a href={detailsLink} class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
<a href={detailsLink} class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{:else if detailsAction}
<button on:click={detailsAction} class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</button
>
<button on:click={detailsAction} class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</button
>
{/if}
{#if deleteEnabled}
<button
tabindex="0"
on:click={deleteAction}
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
<button
tabindex="0"
on:click={deleteAction}
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}

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