Compare commits

..
2 Commits
Author SHA1 Message Date
philipp 06a4428835 Merge pull request 'sync - 0.12.3' (#127) from dev into main
Reviewed-on: #127
Reviewed-by: Nicolai Ort <info@nicolai-ort.com>
2021-04-08 17:28:29 +00:00
niggl bbad338ced Merge pull request 'Merge Minor into main' (#113) from dev into main
Reviewed-on: #113
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-26 16:34:53 +00:00
173 changed files with 12073 additions and 23692 deletions

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
FROM mcr.microsoft.com/vscode/devcontainers/base:alpine-3.12
RUN apk update
RUN apk add --upgrade nodejs-current npm
RUN npm i -g yarn rimraf
RUN rimraf node_modules
RUN yarn set version berry
+20
View File
@@ -0,0 +1,20 @@
{
"name": "Node.js",
"build": {
"dockerfile": "Dockerfile"
},
"settings": {
"terminal.integrated.shell.linux": "/bin/sh"
},
"extensions": [
"dbaeumer.vscode-eslint",
"2gua.rainbow-brackets",
"christian-kohler.npm-intellisense",
"remimarsal.prettier-now",
"svelte.svelte-vscode",
"lokalise.i18n-ally",
"fivethree.vscode-svelte-snippets",
"voorjaar.windicss-intellisense"
],
"postCreateCommand": "yarn && yarn dev --open"
}
+1 -4
View File
@@ -1,4 +1 @@
public/env.sample.js
.pnpm-store
.yarn
.pnp.*
public/env.sample.js
+84
View File
@@ -0,0 +1,84 @@
---
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: pipeline
type: kubernetes
name: build:dev
steps:
- name: run full license export
depends_on: ["clone"]
image: node:alpine
commands:
- yarn
- yarn licenses:export
- 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
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: registry.odit.services/lfk/frontend
tags:
- dev
registry: registry.odit.services
mtu: 1000
trigger:
branch:
- dev
event:
- push
---
kind: pipeline
type: kubernetes
name: build:tags
steps:
- name: build $DRONE_TAG
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: registry.odit.services/lfk/frontend
tags:
- '${DRONE_TAG}'
registry: registry.odit.services
mtu: 1000
trigger:
event:
- tag
-33
View File
@@ -1,33 +0,0 @@
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 -2
View File
@@ -1,8 +1,10 @@
node_modules
package-lock.json
yarn.lock
*.map
public/env.js
public/index.html
/dist
.pnpm-store
.yarn
.pnp.*
.pnp.js
.yarnrc.yml
+13 -11
View File
@@ -1,12 +1,14 @@
{
"recommendations": [
"2gua.rainbow-brackets",
"christian-kohler.npm-intellisense",
"remimarsal.prettier-now",
"svelte.svelte-vscode",
"lokalise.i18n-ally",
"fivethree.vscode-svelte-snippets",
"voorjaar.windicss-intellisense"
],
"unwantedRecommendations": ["antfu.i18n-ally"]
}
"recommendations": [
"2gua.rainbow-brackets",
"christian-kohler.npm-intellisense",
"remimarsal.prettier-now",
"svelte.svelte-vscode",
"lokalise.i18n-ally",
"fivethree.vscode-svelte-snippets",
"voorjaar.windicss-intellisense"
],
"unwantedRecommendations": [
"antfu.i18n-ally"
]
}
+4 -4
View File
@@ -1,7 +1,7 @@
languageIds:
- javascript
- svelte
- html
- javascript
- svelte
- html
monopoly: false
refactorTemplates:
- "{$_('$1')}"
- "{$_('$1')}"
+4 -4
View File
@@ -1,5 +1,5 @@
{
"i18n-ally.localesPaths": "src/locales",
"i18n-ally.keystyle": "nested",
"windicss.enableCodeFolding": false
}
"i18n-ally.localesPaths": "src/locales",
"i18n-ally.keystyle": "nested",
"windicss.enableCodeFolding": false,
}
+1 -878
View File
@@ -2,885 +2,8 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.9.11](https://git.odit.services/lfk/frontend/compare/1.9.10...1.9.11)
- feat(dash): add runnersViaKiosk [`5291f8a`](https://git.odit.services/lfk/frontend/commit/5291f8a4d1721cd0c745191ebc85f221c34a23c8)
#### [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)
#### [1.4.11](https://git.odit.services/lfk/frontend/compare/1.4.10...1.4.11)
> 10 May 2023
- chore(deps): Lockfile [`f77460b`](https://git.odit.services/lfk/frontend/commit/f77460bb0c8ce6d0f3d83a077017d5fc7bf55af7)
- 🚀RELEASE v1.4.11 [`373484c`](https://git.odit.services/lfk/frontend/commit/373484c2424bea7ae0d70d342e0ae2076aab1b6a)
- feat(orgs): Show total distance [`574e0dc`](https://git.odit.services/lfk/frontend/commit/574e0dcb051305bde2fc76d8456a35baec0cf309)
- chore(deps): More bumps [`7b19a0a`](https://git.odit.services/lfk/frontend/commit/7b19a0aa08bb6c89c51d27c0d05777e8fcfdad17)
#### [1.4.10](https://git.odit.services/lfk/frontend/compare/1.4.9...1.4.10)
> 10 May 2023
- chore(deps): Bumped svelte-table [`29a2854`](https://git.odit.services/lfk/frontend/commit/29a2854671b3af5b85ea96d050a9076f47b6575d)
- 🚀RELEASE v1.4.10 [`c3e9c27`](https://git.odit.services/lfk/frontend/commit/c3e9c27cd3d4b916f1661d4958cabab038918587)
- chore(deps): Pin and bump [`8e6786e`](https://git.odit.services/lfk/frontend/commit/8e6786e72227b3f07cc805f0957d5b7fd123ec13)
- chore(deps): Bumped scanclientjs [`6ad4056`](https://git.odit.services/lfk/frontend/commit/6ad40564e3e342046f6ee19fab9e455ec3bbff9b)
#### [1.4.9](https://git.odit.services/lfk/frontend/compare/1.4.8...1.4.9)
> 9 May 2023
- 🚀RELEASE v1.4.9 [`776973b`](https://git.odit.services/lfk/frontend/commit/776973bfe9b34c26a1c80d5e458cc2644dd9036b)
- Changed the in table replacement method [`d9a47f8`](https://git.odit.services/lfk/frontend/commit/d9a47f882c1c6bcf98ef85d50d70c010d54b326e)
- Fixed empty return [`6025e43`](https://git.odit.services/lfk/frontend/commit/6025e43baa8516657a60a1de9a82c2189221c6ac)
#### [1.4.8](https://git.odit.services/lfk/frontend/compare/1.4.7...1.4.8)
> 9 May 2023
- Switched donor loading to non-paginated [`59fe2df`](https://git.odit.services/lfk/frontend/commit/59fe2dfabb224863876c4db31a965c34a51a9369)
- 🚀RELEASE v1.4.8 [`4235758`](https://git.odit.services/lfk/frontend/commit/4235758a6d1499715287d6ab193cc87c68d5742e)
#### [1.4.7](https://git.odit.services/lfk/frontend/compare/1.4.6...1.4.7)
> 4 May 2023
- Paginated modal data loading [`a8a7711`](https://git.odit.services/lfk/frontend/commit/a8a771114df6eb57d5b1d5497a5be49e619d4102)
- Moved loading to onmount [`4e0a2c8`](https://git.odit.services/lfk/frontend/commit/4e0a2c83015bde5e360c5fb2c0babbeaa03dc2b5)
- 🚀RELEASE v1.4.7 [`6364536`](https://git.odit.services/lfk/frontend/commit/6364536dcd840c71f7cb6afb31bbc4f160ac4f73)
#### [1.4.6](https://git.odit.services/lfk/frontend/compare/1.4.5...1.4.6)
> 4 May 2023
- 🚀RELEASE v1.4.6 [`b6fed92`](https://git.odit.services/lfk/frontend/commit/b6fed92a176af1c975484d9146ee5634e0031401)
- fix(donor/details): don't load donations [`a2ff5b8`](https://git.odit.services/lfk/frontend/commit/a2ff5b8a142ce4e6b8876f64935f9787ec44a51e)
- fix(donor/detail): Set email to null to avoid vaidation errors [`97b57ae`](https://git.odit.services/lfk/frontend/commit/97b57aeb0cc9058542a36dea9c8b2852169c250f)
- fix(donor/detail): Set phone to null to avoid vaidation errors [`e25ed1f`](https://git.odit.services/lfk/frontend/commit/e25ed1fff9b200605d5d2b78238b774ec7289aaa)
#### [1.4.5](https://git.odit.services/lfk/frontend/compare/1.4.4...1.4.5)
> 4 May 2023
- Revert "revert: buggy pagination" [`dacb2f8`](https://git.odit.services/lfk/frontend/commit/dacb2f8ace373f6594fc64af133971af053f00c0)
- fix: Removed dynamic pagesize adjustments [`803d64c`](https://git.odit.services/lfk/frontend/commit/803d64c78caa570d31d6055e70e2d2af6834f04b)
- 🚀RELEASE v1.4.5 [`0284f18`](https://git.odit.services/lfk/frontend/commit/0284f18beb8b24d4d4d071eca13bc5868666232c)
#### [1.4.4](https://git.odit.services/lfk/frontend/compare/1.4.3...1.4.4)
> 4 May 2023
- 🚀RELEASE v1.4.4 [`b7a5396`](https://git.odit.services/lfk/frontend/commit/b7a53960e5f37ae089d77bc11668d917145e2abb)
- fix(AddDonationModal): missing toast dismiss on success distance donation [`66f1e6b`](https://git.odit.services/lfk/frontend/commit/66f1e6b4fe1350ee79673a0aff97e36f44179c92)
#### [1.4.3](https://git.odit.services/lfk/frontend/compare/1.4.2...1.4.3)
> 4 May 2023
- revert: buggy pagination [`b264864`](https://git.odit.services/lfk/frontend/commit/b2648645e8fc05f8742ecfc592557f954261671b)
- 🚀RELEASE v1.4.3 [`33166bf`](https://git.odit.services/lfk/frontend/commit/33166bfafcffb9d86dfc7dfcd2cb8ba5c85da7e7)
#### [1.4.2](https://git.odit.services/lfk/frontend/compare/1.4.1...1.4.2)
> 4 May 2023
- 🚀RELEASE v1.4.2 [`53e3ddb`](https://git.odit.services/lfk/frontend/commit/53e3ddb751c1150a4640ae6302e4df5b88cedc51)
- fix(GenerateRunnerCertificates): missing toast import [`d49f545`](https://git.odit.services/lfk/frontend/commit/d49f545d94acabc0c96860f212466b7a4cbe7dab)
- fix(DonorDetail): missing toast import [`edc2dca`](https://git.odit.services/lfk/frontend/commit/edc2dcab92c3cace05335a283a849c3c978ec8ec)
#### [1.4.1](https://git.odit.services/lfk/frontend/compare/1.4.0...1.4.1)
> 1 May 2023
- 🚀RELEASE v1.4.1 [`3b98c99`](https://git.odit.services/lfk/frontend/commit/3b98c99b72f24b8552e2b2334f13622bdf6ef90d)
- Fixed translation [`1da775a`](https://git.odit.services/lfk/frontend/commit/1da775a09b8be90a49e06aed16df917d221ee989)
#### [1.4.0](https://git.odit.services/lfk/frontend/compare/1.3.4...1.4.0)
> 1 May 2023
- formatting, full migration to svelte-french-toast [`46d076a`](https://git.odit.services/lfk/frontend/commit/46d076af9d65ebb11504a7e6879753780b69db2c)
- drop gridjs (TracksOverview Actions will need to be re-implemented) [`8b92230`](https://git.odit.services/lfk/frontend/commit/8b922309b990c42fcfd57b939abacf4d8c99e638)
- 🚀RELEASE v1.4.0 [`f0475bd`](https://git.odit.services/lfk/frontend/commit/f0475bd9a08d99f58b4d3dce584cd6a3a8630e56)
- Added track update toasts [`103ad57`](https://git.odit.services/lfk/frontend/commit/103ad57ddc8a35ff971bef44053a9e32a7b68233)
- drop legacy ThFilter components [`bc4ac0f`](https://git.odit.services/lfk/frontend/commit/bc4ac0f3160571cd412361de82ef4555ee068677)
- text cleanups, StatCard improvements [`a2f9dbb`](https://git.odit.services/lfk/frontend/commit/a2f9dbbe014b5ae9705e8e7b6944f7f7c576d22e)
- Updated the track editing switch logic [`a953349`](https://git.odit.services/lfk/frontend/commit/a953349c1478b912e08f88c1fb70c74af0bc9bbb)
- chore(deps): bump all [`6154ca7`](https://git.odit.services/lfk/frontend/commit/6154ca7ddfb8b6ad0e1644b8c6756d51f2fbb858)
- svelte-french-toast + translations [`8fb1e0c`](https://git.odit.services/lfk/frontend/commit/8fb1e0ca0f51c90270fb5e1a05be5e8273238a2c)
- Implemented delete for new track table [`081a141`](https://git.odit.services/lfk/frontend/commit/081a141218ab7de2620f7b06083697368d44bf6c)
- striped tabled [`5e82638`](https://git.odit.services/lfk/frontend/commit/5e82638f3594298d0542cd03d5d6aa80aa383b9d)
- add svelte-french-toast [`56c3365`](https://git.odit.services/lfk/frontend/commit/56c33656562079bb773491c8aecedea3f6acdb74)
- Editing update logic [`2856c5c`](https://git.odit.services/lfk/frontend/commit/2856c5c1b786f732b0db80324ea74513e8be186d)
- monospace fonts for IDs in overviews [`811f5d5`](https://git.odit.services/lfk/frontend/commit/811f5d575496be43e5e48197813112d35e79a81f)
- Full track table editing [`e9cf2bc`](https://git.odit.services/lfk/frontend/commit/e9cf2bc8498fc02332059880d7a6994348165b76)
- drop propfilepic [`6952b87`](https://git.odit.services/lfk/frontend/commit/6952b8727f06c520cb60a00acfde1dff52d4f345)
- fix: scan laptime formatting [`01b415d`](https://git.odit.services/lfk/frontend/commit/01b415d4cb147879e959e86d053dc02cae8cfdc9)
- Dynamicly adjust page size for large datasets [`064197d`](https://git.odit.services/lfk/frontend/commit/064197d2226da772907099ecf96c3ab984c9af59)
- ScansOverview full month formatting [`69ec7fc`](https://git.odit.services/lfk/frontend/commit/69ec7fc1fecc67751643ce35f22925f3132b8792)
- translations [`8be40e2`](https://git.odit.services/lfk/frontend/commit/8be40e2d80336f72989deb3e5e20a7cd8f7fb6f1)
- drop middlename for admin users [`daeea24`](https://git.odit.services/lfk/frontend/commit/daeea24e0e99b8a95665167d62d0ee830bdea3de)
- add missing striped tables [`8eaad82`](https://git.odit.services/lfk/frontend/commit/8eaad8219a109fa8b4bd1f719d7079bff8b7c041)
- translation cleanups [`663cb29`](https://git.odit.services/lfk/frontend/commit/663cb29ccde4fa15317f764147187c5b82e748d5)
- Added edit button for trackscans [`175d867`](https://git.odit.services/lfk/frontend/commit/175d86745fb9bfce03fe5f5c638b52467b688938)
- a11y cleanup [`6bc92f4`](https://git.odit.services/lfk/frontend/commit/6bc92f4a080f0c506793866d99c97ccb87ba15b8)
- a11y fix OrgOverview [`0fca035`](https://git.odit.services/lfk/frontend/commit/0fca0352c59cdccb99716355591f88ff573ac949)
- README update [`d32eb82`](https://git.odit.services/lfk/frontend/commit/d32eb8266b0e9daec4b9ba52832d5e5118abec45)
- a11y improvements [`4c81e3c`](https://git.odit.services/lfk/frontend/commit/4c81e3c43218be4b23d137b386520c71d19f5844)
- cleanup legacy TeamsOverview text [`6c1a707`](https://git.odit.services/lfk/frontend/commit/6c1a70716665d57f1326c4475030ae15a7c459e0)
- fix: a11y in About page [`e904ab0`](https://git.odit.services/lfk/frontend/commit/e904ab0b8494ff57579c8954a8eb713fc223a88f)
- improved login [`dbaf857`](https://git.odit.services/lfk/frontend/commit/dbaf85799ac9e56d8760450cfe357df016f10da7)
- chore(deps): node:20.0.0 [`81d4da6`](https://git.odit.services/lfk/frontend/commit/81d4da655099100c631d450caafbf7039fa20592)
- cleanup MainDashContent [`763a01a`](https://git.odit.services/lfk/frontend/commit/763a01af09b36004ceccfa6b182b7dc5ea070128)
- Updated store directory for dockerfil [`bbf8170`](https://git.odit.services/lfk/frontend/commit/bbf8170cb98410bbcd8dc51bb122beee615312ee)
- Bump dockerfile node [`15d8afe`](https://git.odit.services/lfk/frontend/commit/15d8afefbb6b697a6cbdb2d803a7d8edcea4e650)
- Pinned config files used [`f3bcc01`](https://git.odit.services/lfk/frontend/commit/f3bcc01685f3ea3ef6786a8e7d9a5b1a4f829d53)
- Switched build image node version [`9523860`](https://git.odit.services/lfk/frontend/commit/95238606d52ca58985b91ea03f7e9f490fdf2310)
- Merge pull request 'next' (#180) from next into dev [`8c628f2`](https://git.odit.services/lfk/frontend/commit/8c628f23dcfb1f6f120d19bb3ecdb422ca5093cd)
- improved StatCard readability [`7d82536`](https://git.odit.services/lfk/frontend/commit/7d8253618b18719549824ed19e024b8828c9df06)
- new license file version [CI SKIP] [`38a6650`](https://git.odit.services/lfk/frontend/commit/38a665024eb1df3eba66c61d8cb3199000b629e5)
#### [1.3.4](https://git.odit.services/lfk/frontend/compare/1.3.3...1.3.4)
> 19 April 2023
- 🚀RELEASE v1.3.4 [`e7b2c64`](https://git.odit.services/lfk/frontend/commit/e7b2c647981111650b3e2e471f4b5195fa6b65b6)
- Smaller sponsoring page size [`7cb6b63`](https://git.odit.services/lfk/frontend/commit/7cb6b63eb9596da4ee84369b220c3e680c607032)
#### [1.3.3](https://git.odit.services/lfk/frontend/compare/1.3.2...1.3.3)
> 19 April 2023
- 🚀RELEASE v1.3.3 [`d6d88f5`](https://git.odit.services/lfk/frontend/commit/d6d88f5f60716ca496a17f09b835b23223ec495d)
- bumped lfk-client-js [`2c208c4`](https://git.odit.services/lfk/frontend/commit/2c208c438185892270a0ebd37deb6a7c9ac08fc0)
#### [1.3.2](https://git.odit.services/lfk/frontend/compare/1.3.1...1.3.2)
> 19 April 2023
- 🚀RELEASE v1.3.2 [`39bc6c4`](https://git.odit.services/lfk/frontend/commit/39bc6c49450964510f996369d014f92c569188ae)
- fix(donors): Shortened texts [`b94e3b7`](https://git.odit.services/lfk/frontend/commit/b94e3b745f2febbe91e16a7a26f96b47d347ab92)
- feat(donations): Resolve donations via donor [`6f337ae`](https://git.odit.services/lfk/frontend/commit/6f337aeee16267d1e67e3d3855b63b6f2e57979f)
- fix(donors): Removed debug infos [`5d48060`](https://git.odit.services/lfk/frontend/commit/5d48060834717b2244172a0914e2690f8fe634d9)
#### [1.3.1](https://git.odit.services/lfk/frontend/compare/1.3.0...1.3.1)
> 19 April 2023
- feat(donations): Donation table filtering [`91ab199`](https://git.odit.services/lfk/frontend/commit/91ab199769c9f4f8051c74ad43a701db321f3995)
- feat(donors): Added name and address filtering [`27b4dde`](https://git.odit.services/lfk/frontend/commit/27b4dde7551995c9d7e8ca33a9bd97d429a35801)
- 🚀RELEASE v1.3.1 [`c842c20`](https://git.odit.services/lfk/frontend/commit/c842c203e2fbf0a201297d475db9047c0691bd52)
- More filtering [`5bcfc8d`](https://git.odit.services/lfk/frontend/commit/5bcfc8db752fce96e9f523d14cefff1a4f675661)
#### [1.3.0](https://git.odit.services/lfk/frontend/compare/1.2.0...1.3.0)
> 19 April 2023
- feat(donations): Implemented donation deletion via confirm modal [`505fb8c`](https://git.odit.services/lfk/frontend/commit/505fb8cb08b81a7dcb08561bdda0f6464f140d3e)
- 🚀RELEASE v1.3.0 [`e75be49`](https://git.odit.services/lfk/frontend/commit/e75be49be42c3d5581e2204bfa064bfa3778c1b6)
- feat(donationsoverview): Switched donations overview to datatable [`133470b`](https://git.odit.services/lfk/frontend/commit/133470b6f2a63ec087f27c98ef260648a8672e5f)
- feat(donations): Implemented add donation payment via datatable refresh [`e5c9265`](https://git.odit.services/lfk/frontend/commit/e5c92655886ad9a6fcd7565fadd7955c477c3595)
- feat(donations): Donations reactive create and load into datatable [`02003ec`](https://git.odit.services/lfk/frontend/commit/02003ec80efc16aabd126710a6eeac18df43f841)
- feat(DonationsOverview): i18n loading text [`8f8858f`](https://git.odit.services/lfk/frontend/commit/8f8858f10071ddf9988d0ec0e3c4a891db24a102)
- new license file version [CI SKIP] [`4289034`](https://git.odit.services/lfk/frontend/commit/4289034436869750205a946247e7ab5f9892fe98)
#### [1.2.0](https://git.odit.services/lfk/frontend/compare/1.1.0...1.2.0)
> 19 April 2023
- feat(donoroverview): Added datatable formatters [`d98fb0d`](https://git.odit.services/lfk/frontend/commit/d98fb0d5b288c987a45ccbf2bb026ccaab539a71)
- 🚀RELEASE v1.2.0 [`fdc7d80`](https://git.odit.services/lfk/frontend/commit/fdc7d80bbf9bd698128e9ec4f91fa813499777a9)
- feat(donors): Load donors paginated [`5014bf5`](https://git.odit.services/lfk/frontend/commit/5014bf5bc5873cfe4ae04d71b4aff12b257dd2e3)
- feat(donorsoverview): Dynamicly add newly generated donors [`352551e`](https://git.odit.services/lfk/frontend/commit/352551e168b5dced5e7353e82655908d82d28af0)
- feat(donorsoverview): Implemented delete confirmation with datatable [`7aec050`](https://git.odit.services/lfk/frontend/commit/7aec050419f6f1bf853c3e1bc655b01725ed3b65)
#### [1.1.0](https://git.odit.services/lfk/frontend/compare/1.0.0...1.1.0)
> 19 April 2023
- 🚀RELEASE v1.1.0 [`0708cab`](https://git.odit.services/lfk/frontend/commit/0708cabc75e63a876e54a0b343318f8d934ae319)
- feat(dashboar): Added total donors to overview [`e0b6148`](https://git.odit.services/lfk/frontend/commit/e0b61486b089aa1e611ef3569b1521fc331ec0e4)
- feat(dashboard): Updated stats icons [`4fcb26c`](https://git.odit.services/lfk/frontend/commit/4fcb26cf9371e27e5d7e474b3558ef354e9114c0)
- feat(dashboard): Added average sponsoring [`269def2`](https://git.odit.services/lfk/frontend/commit/269def20d114ededaba3153bbd50ec2ddd70e1c6)
- feat(dashboard): Added total donations [`7b2b598`](https://git.odit.services/lfk/frontend/commit/7b2b59858839b98370af6fb1e6028ba0a1639186)
- feat(dashboard): Added average distance [`b8de9e0`](https://git.odit.services/lfk/frontend/commit/b8de9e0e427b3a8b56e6354ad7168ae12c7cce85)
- Lockfile [`3f86f74`](https://git.odit.services/lfk/frontend/commit/3f86f7412ffc4bc27328ad1f7d3c3118546e7e29)
- Bumped client [`6454d96`](https://git.odit.services/lfk/frontend/commit/6454d960de3f9f5ea86679f157b3b7e7cffde74d)
- new license file version [CI SKIP] [`2a64094`](https://git.odit.services/lfk/frontend/commit/2a640940062765a470387103a72ed14a2411d97b)
### [1.0.0](https://git.odit.services/lfk/frontend/compare/0.19.0...1.0.0)
> 19 April 2023
- 🚀RELEASE v1.0.0 [`8da7578`](https://git.odit.services/lfk/frontend/commit/8da7578a0a46a3e97d8c870e29399f6e8821c9fa)
- Merge pull request 'feature/175-request_pagination' (#176) from feature/175-request_pagination into dev [`e9ce964`](https://git.odit.services/lfk/frontend/commit/e9ce9644ff03f981cec6e9ad56aa5fdf0ff71ef4)
- Donation paginated loading [`ccf8656`](https://git.odit.services/lfk/frontend/commit/ccf865687b34016931a702c0a9b98a0a18e2b03a)
- Paginated scan loading [`cac34db`](https://git.odit.services/lfk/frontend/commit/cac34db1fd3bf5dc7c7be64b3a76ca4c8c77938d)
- Implemented Async loading of cards via pagination (500 cards per request) [`c33dfcf`](https://git.odit.services/lfk/frontend/commit/c33dfcfddddfed0902f3fa9b1d8a1d3e1560262f)
- Paginated runner loading (1000 per page) [`faf3893`](https://git.odit.services/lfk/frontend/commit/faf3893180bb735bea6f1ea58c896686b89949fe)
- Allways set loaded to true [`52439aa`](https://git.odit.services/lfk/frontend/commit/52439aa5bc8cfb1d78d5dfce55b1a0df640ad8f5)
- Bumped lfk client [`019e14a`](https://git.odit.services/lfk/frontend/commit/019e14ab1f99906f13d36c7148d0f4b7894072f2)
- new license file version [CI SKIP] [`a6ce04c`](https://git.odit.services/lfk/frontend/commit/a6ce04c90386f16abf235cc7b2f95aeea5011c7d)
#### [0.19.0](https://git.odit.services/lfk/frontend/compare/0.18.4...0.19.0)
> 17 April 2023
- 🚀RELEASE v0.19.0 [`94a64ca`](https://git.odit.services/lfk/frontend/commit/94a64ca69078c7fe2935eeb5f955fab95a79cb85)
- Merge pull request 'feature/173-scanstation_configcodes' (#174) from feature/173-scanstation_configcodes into dev [`165c154`](https://git.odit.services/lfk/frontend/commit/165c1542338c58f2abf42fef2e7b84b40d1e2d9c)
- I18n [`e60c09e`](https://git.odit.services/lfk/frontend/commit/e60c09e19c9cc20338906e84f4db4e009d926360)
- Implemented config code generation [`4b63427`](https://git.odit.services/lfk/frontend/commit/4b6342727ee0ea38597750d8c99edc301f1ccc2d)
- Styling [`4834d14`](https://git.odit.services/lfk/frontend/commit/4834d1484c3fb6ecd4a1b56aa9fbb8125c641a62)
- Adjusted size on smaller devices [`318547d`](https://git.odit.services/lfk/frontend/commit/318547db46045e41de64d5688368e85cd6fb8035)
- Lockfile [`947d01c`](https://git.odit.services/lfk/frontend/commit/947d01cf7fc7fe2ee88c56e017b0d663f1f3b4f9)
- Barcode placeholder [`cb5fa52`](https://git.odit.services/lfk/frontend/commit/cb5fa52cd9a97490b50fb0c02c26615b49650c08)
- Added bwip-js for barcode generation [`3563394`](https://git.odit.services/lfk/frontend/commit/3563394fb33d661890327e2ae08c400830b37844)
#### [0.18.4](https://git.odit.services/lfk/frontend/compare/0.18.3...0.18.4)
> 15 April 2023
- 🚀RELEASE v0.18.4 [`269d7a7`](https://git.odit.services/lfk/frontend/commit/269d7a7defdde059ef2bb5103262cf734e9babe9)
- Hide address2 in orgs by default [`e95f233`](https://git.odit.services/lfk/frontend/commit/e95f2333b0b958ed00c0e097b43aac2e70ad0d38)
#### [0.18.3](https://git.odit.services/lfk/frontend/compare/0.18.2...0.18.3)
> 15 April 2023
- 🚀RELEASE v0.18.3 [`950217e`](https://git.odit.services/lfk/frontend/commit/950217e0a350f9999b879475edf41f2f11c48179)
- Dont show adress 2 in runner overview [`5e65fb3`](https://git.odit.services/lfk/frontend/commit/5e65fb33013c3dad38e7ad6740b017ae206f278f)
#### [0.18.2](https://git.odit.services/lfk/frontend/compare/0.18.1...0.18.2)
> 15 April 2023
- 🚀RELEASE v0.18.2 [`2a294cd`](https://git.odit.services/lfk/frontend/commit/2a294cde040044bbebfb9c8b34b6c91b27772741)
- Added timestamps to scanoverview [`cffbd17`](https://git.odit.services/lfk/frontend/commit/cffbd17dc77054048cc9b14891f960f9a3fd18cb)
- Push in releaseit [`e95420d`](https://git.odit.services/lfk/frontend/commit/e95420d79c3227c0ca0cf0c0b599970c2b7d690e)
#### [0.18.1](https://git.odit.services/lfk/frontend/compare/0.18.0...0.18.1)
> 15 April 2023
- 🚀RELEASE v0.18.1 [`00de8c3`](https://git.odit.services/lfk/frontend/commit/00de8c3d75e90cd4614f42111f5f45bedde64130)
- Missing scanstation translations [`30e3396`](https://git.odit.services/lfk/frontend/commit/30e33968978bf33cedb31bcbf63fac273e1664f5)
- fix: button onclick a11y [`9fe53b0`](https://git.odit.services/lfk/frontend/commit/9fe53b0b9c71e8a6b4aa3f317327ffe729df0834)
- fix(ConfirmStatsClientDeletion): ScanStationService -&gt; StatsClientService [`5291e04`](https://git.odit.services/lfk/frontend/commit/5291e049a1d2e880fbe277095da91b70d4812c3f)
- fix(ConfirmScanStationDeletion): donorControllerRemove -&gt; scanStationControllerRemove [`08fbb50`](https://git.odit.services/lfk/frontend/commit/08fbb504c958415ce75e1e426296f870f0f1358d)
#### [0.18.0](https://git.odit.services/lfk/frontend/compare/0.17.3...0.18.0)
> 12 April 2023
- Moved filter function to typed version [`#171`](https://git.odit.services/lfk/frontend/issues/171)
- ScansOverview: migrate to datatable [`#168`](https://git.odit.services/lfk/frontend/issues/168)
- 🚀RELEASE v0.18.0 [`eb80406`](https://git.odit.services/lfk/frontend/commit/eb80406fdb8abf3f76bca742095e8f1f03480a56)
- wip: ScansOverview -&gt; new datatable [`c87561f`](https://git.odit.services/lfk/frontend/commit/c87561f63b90ab951daf91d9b8b54ba96a94cc7f)
- Basic card table replace [`5662c3b`](https://git.odit.services/lfk/frontend/commit/5662c3b6da67c00c94254bf39f8820e531fc93ef)
- RunnersOverview: table responsiveness [`bf1e715`](https://git.odit.services/lfk/frontend/commit/bf1e715261c0076fd8543dd1187c516209a73b16)
- Basic card delete modal [`8ffe8ef`](https://git.odit.services/lfk/frontend/commit/8ffe8eff0623767809cdc49ea15cf2d30b609285)
- wip: delete scans [`f105cc0`](https://git.odit.services/lfk/frontend/commit/f105cc0a41972261610d03015ecd5ee492dab8e2)
- Added delete runner modal [`fd5db7d`](https://git.odit.services/lfk/frontend/commit/fd5db7d68ab4b32f7bb14bd0275a447b749e1fd8)
- Extracted table bottom [`8f50555`](https://git.odit.services/lfk/frontend/commit/8f50555a06a35d0b11ac1d9201851d00d915c4b8)
- fix: formatting [`9def0b2`](https://git.odit.services/lfk/frontend/commit/9def0b27c9958c5c75466f56c17b6dc4d44d2b50)
- fix(CardsOverview): table scroll + checkbox posititioning [`9c13b2f`](https://git.odit.services/lfk/frontend/commit/9c13b2f9e938ad36cb74c66a8f91a5b872c8c2d1)
- Extracted table header [`c241961`](https://git.odit.services/lfk/frontend/commit/c241961d0ab0d6b8c3bf0453c1a21d84027ed62f)
- drop legacy dependencies [`c98eb49`](https://git.odit.services/lfk/frontend/commit/c98eb49ae31d1d28de79c7a9c945ade50828f07f)
- scan delete working [`c7dcf7c`](https://git.odit.services/lfk/frontend/commit/c7dcf7c66d7141117e08462ad05f6f441565d012)
- Add Card appends current cards [`195d182`](https://git.odit.services/lfk/frontend/commit/195d182cc977b4ac9f342f09a9ea69d461892a95)
- Moved data loading to onmount [`a35af6f`](https://git.odit.services/lfk/frontend/commit/a35af6f02055115d60d040baaf22dabbeda38498)
- Add runners reactivity [`06411dc`](https://git.odit.services/lfk/frontend/commit/06411dc14747fc9803da009a29688789c31dfc42)
- quick cleanup [`ebdd1c2`](https://git.odit.services/lfk/frontend/commit/ebdd1c2c0c2191c60dfacd0c4be989748628ca63)
- Extracted deletion into function of overview [`a0727a0`](https://git.odit.services/lfk/frontend/commit/a0727a02913708de06be8bf1b9f8cbae5489f080)
- Moved update card logic to overview [`53b945c`](https://git.odit.services/lfk/frontend/commit/53b945c72fa5b30f03eff8bb83f8f25d85d79b07)
- wip:ScanValid badge [`bd4952e`](https://git.odit.services/lfk/frontend/commit/bd4952ee575b4cd8caea3608b14d8c51b37a55eb)
- yeeted extra table styling [`52a02c8`](https://git.odit.services/lfk/frontend/commit/52a02c82d2bdabdf3e632910aa415c1ffb97fd7f)
- Added custom runner filter [`7c6d39b`](https://git.odit.services/lfk/frontend/commit/7c6d39b5fa60d1b834c9fb74dd26dfc2225161d6)
- Scan reactive add [`f5d14f2`](https://git.odit.services/lfk/frontend/commit/f5d14f2e1810462cba99b2482320746f0750d4a1)
- Moved code around [`239f79f`](https://git.odit.services/lfk/frontend/commit/239f79fecba732c807f4296cf524be15c1ccff1e)
- Scan deletion [`7d8c68a`](https://git.odit.services/lfk/frontend/commit/7d8c68a4550407e8c871545e7a0c5f622a1e3b9c)
- Added status filter function [`f6c1fea`](https://git.odit.services/lfk/frontend/commit/f6c1fea17ce64d890ac810d9dff058dd589c2989)
- Added delete toast [`e2ddb5a`](https://git.odit.services/lfk/frontend/commit/e2ddb5a14cc8b450c00129709bf5b933a47866dd)
- Fixed killing of the dom [`2f62c7a`](https://git.odit.services/lfk/frontend/commit/2f62c7ae89b7dda3bc0efd4d56e677898a3f197f)
- Cards details modal [`f6985da`](https://git.odit.services/lfk/frontend/commit/f6985daec71e4d922acd8a9b2673fd41317b075f)
- Reload table data on delete [`3e8dac3`](https://git.odit.services/lfk/frontend/commit/3e8dac3203f56723a3dad4a35887d60fc03d4ae3)
- Added middle-name [`d811058`](https://git.odit.services/lfk/frontend/commit/d8110580e9a33cfb0c6e9cbdce630262d5b5d4c1)
- Fixed id sorting [`7cec2a0`](https://git.odit.services/lfk/frontend/commit/7cec2a00c513678cae8643ef3905d566a538bde0)
- Import add to datatable [`2a91562`](https://git.odit.services/lfk/frontend/commit/2a915620c9adcb481be53e3c919bd5f14a5108ee)
- Scans deletion [`6d9d8a4`](https://git.odit.services/lfk/frontend/commit/6d9d8a4724135df98ed1bec74b0cc20a8bdbda53)
- fix(DeleteRunnerModal): ESC key [`1df505e`](https://git.odit.services/lfk/frontend/commit/1df505ea00968f65e7aff85baa80f8935fd2eb7b)
- Versionbuilder [`1613ae7`](https://git.odit.services/lfk/frontend/commit/1613ae7de6e84aa5f9df6774a8e820fcf515b14a)
- Fixed edit dispatch [`c681570`](https://git.odit.services/lfk/frontend/commit/c68157013431eeb1150e37fea87da02d5f7b032e)
- ScansOverview: use CardRunner link [`4b171fd`](https://git.odit.services/lfk/frontend/commit/4b171fd04f4fc895f9919294858e546686504c65)
- Added custom filter to scan overview [`2c198cf`](https://git.odit.services/lfk/frontend/commit/2c198cfde89363142c1c958e6961b7823196e04e)
- Disable sort for actions [`fc2c290`](https://git.odit.services/lfk/frontend/commit/fc2c2907c43e5b1fa192b571cf93b2f2e492158f)
- Delete modal logic [`da33005`](https://git.odit.services/lfk/frontend/commit/da3300562a25cf39945a94f873ec27f0d5fb9dc0)
- Module [`9ae5e62`](https://git.odit.services/lfk/frontend/commit/9ae5e62e5d84097082bc29fe20b329f6e614f6ed)
- new license file version [CI SKIP] [`ed1caa7`](https://git.odit.services/lfk/frontend/commit/ed1caa7be7673ef08f9c0074101f3cfb52963ab7)
- Merge pull request 'experiment/tanstack' (#172) from experiment/tanstack into dev [`d88f3a5`](https://git.odit.services/lfk/frontend/commit/d88f3a5a2737921536c5eb109734abafd7609e51)
- Removed pnpm store [`eb13f03`](https://git.odit.services/lfk/frontend/commit/eb13f038a1990ed2966552ab1d36df4a76e5a99a)
- dependency update, drop focusTrap, first tanstack experiment in RunnersOverview [`9bec95e`](https://git.odit.services/lfk/frontend/commit/9bec95ede80908e45ad0f18a66dbb45784f7e33e)
- Added translations [`dcabed4`](https://git.odit.services/lfk/frontend/commit/dcabed4e93134a1f04305c47536f1cc93b0cfb31)
- new license file version [CI SKIP] [`d0fe6a2`](https://git.odit.services/lfk/frontend/commit/d0fe6a2e8513638c4d8e2b3dafd48b91490eab3b)
- Bumped pnpm to 8 [`ee91748`](https://git.odit.services/lfk/frontend/commit/ee91748b3c7c2fb4f196d76121713ac42465b9dd)
- drop old datatables [`cb5f2b7`](https://git.odit.services/lfk/frontend/commit/cb5f2b73d0a5f71875f48d381382919f9bee364e)
- wip: pagination [`a59dbbe`](https://git.odit.services/lfk/frontend/commit/a59dbbe50ede47e0dada906d10887cc6b1ae3264)
- Added sort [`842248e`](https://git.odit.services/lfk/frontend/commit/842248e4c43bb94a0e73981d2d848e0723687932)
- cleanup headers [`5ecf838`](https://git.odit.services/lfk/frontend/commit/5ecf838dd231269c3c4f0d1e3376ff157457b04a)
- Moved filter function to shared [`2304b12`](https://git.odit.services/lfk/frontend/commit/2304b12c1cca6a64573223906ab0561ba9050ec5)
- Group filters [`0283df2`](https://git.odit.services/lfk/frontend/commit/0283df22c84740dd978e09120985215b65d7a12a)
- feat(RunnersOverview): row selection [`6993511`](https://git.odit.services/lfk/frontend/commit/6993511c67cc81303a1eece7fc2a4218dc818afc)
- Basic details and delete buttons [`9363773`](https://git.odit.services/lfk/frontend/commit/9363773fa1910975d48746572fe166363986c6ab)
- Added delete cards button [`70307a9`](https://git.odit.services/lfk/frontend/commit/70307a9e8272f22801c6765c947f8576cc1d1102)
- ScansOverview: add ThFilterTrack [`008835c`](https://git.odit.services/lfk/frontend/commit/008835c24f833aebbecc53921b0901a76f25bf06)
- Yeeted old datatable references [`8925261`](https://git.odit.services/lfk/frontend/commit/89252619b1f6d478c287178bfffc573cf574a8fa)
- Added filter [`a9cdac4`](https://git.odit.services/lfk/frontend/commit/a9cdac4f74a80afe3d0ce677ed5256201b75b29b)
- Moved sort onclick to header only [`3de7b63`](https://git.odit.services/lfk/frontend/commit/3de7b632e058e9ca9ab951de9e86cc2a40eceb68)
- Switched drone to pnpm cache [`d79608e`](https://git.odit.services/lfk/frontend/commit/d79608edbb7ca68b0a26f6c021a141373308081f)
- pagination size [`45a7a90`](https://git.odit.services/lfk/frontend/commit/45a7a90cb8ee933d064c5891b3532cb1ece38d38)
- RunnersOverview: add filter keyboard support [`9415584`](https://git.odit.services/lfk/frontend/commit/94155845f020679e8765d198b5ab4735e1d1142c)
- RunnersOverview: pass selectedRunners to actions [`d4ab76e`](https://git.odit.services/lfk/frontend/commit/d4ab76ea1be1789aaa53ca57d33b2356c6804ffd)
- fix pagination next prev [`cac851f`](https://git.odit.services/lfk/frontend/commit/cac851f2b19dafa01b08d1da612e34cbe3eb4133)
- Moved docker to pnpm with cache [`4cbd265`](https://git.odit.services/lfk/frontend/commit/4cbd26580e51b854fdfd369056f24a30b04100a1)
- fix: TracksOverview [`c799088`](https://git.odit.services/lfk/frontend/commit/c7990882cfa4ac6c3a2ffa696dcbf3b62790c1fe)
- Table header i18n [`070a20a`](https://git.odit.services/lfk/frontend/commit/070a20a2e599e9f00b1bd0e944f9d68d08dc1cba)
- default to 50 rows pagination [`dc866dd`](https://git.odit.services/lfk/frontend/commit/dc866dd5403ae681193c5e325b1f6d4068f80b61)
- improved tablefilters/groupFilter [`3abf608`](https://git.odit.services/lfk/frontend/commit/3abf608b15fcfc2cf65b705d28d394917e7ac333)
- Updated group name conversion [`9111ad1`](https://git.odit.services/lfk/frontend/commit/9111ad147c7537036de834f80db6e557d8d6f0d9)
- show certificate, runnercard, sponsoring contract section [`49c2cd5`](https://git.odit.services/lfk/frontend/commit/49c2cd5c4b32e1b75fe0dae48c1b470608936704)
- add basic table styling [`c5e8409`](https://git.odit.services/lfk/frontend/commit/c5e84090795636c7a80d6cabe46168e436d40f50)
- Cards deleted migrations [`ef077b4`](https://git.odit.services/lfk/frontend/commit/ef077b4e6ad722e6d1c9efd5060165f649fd94c9)
- ignore pnpm store from now on [`fe62ad5`](https://git.odit.services/lfk/frontend/commit/fe62ad5539bc94b242c30c2952bf4c115cc9abfd)
- ammendme [`d31fe23`](https://git.odit.services/lfk/frontend/commit/d31fe2363b0f167b937f7d67908e75c709b324d8)
- updated default table row count [`245db06`](https://git.odit.services/lfk/frontend/commit/245db0617306bab23993caf00dc5ba0405e95625)
- fix: checkbox styling [`64db553`](https://git.odit.services/lfk/frontend/commit/64db5531858b2275d27c53a347da6c0416a278e8)
- Added distance conversion [`333214a`](https://git.odit.services/lfk/frontend/commit/333214aa8f945975ee2dcbd1cfa285e5e75c9d02)
- Added filterfunctions [`a9a965d`](https://git.odit.services/lfk/frontend/commit/a9a965d6981b07b02e3f752e65e1e5c4e9df7147)
- card/ThFilterRunner: text align left [`7083b3d`](https://git.odit.services/lfk/frontend/commit/7083b3d8d2c81fa4ab14f21d379befbb2dab5b6c)
- import [`0240e1d`](https://git.odit.services/lfk/frontend/commit/0240e1dca2e162b8c79f294f5c651b4a38455235)
- fix: table sort button + search style [`57dce34`](https://git.odit.services/lfk/frontend/commit/57dce34fc5b04c297c5341db24ec47c2f1d39595)
- No longer switching pnpm store path [`e5241d6`](https://git.odit.services/lfk/frontend/commit/e5241d619beda90d271485b6c09142b1e7b9b5f1)
- Reactivated generate cards modal [`8f33640`](https://git.odit.services/lfk/frontend/commit/8f33640bec8f79987b1319fb0765535fe493d496)
- Always load bulk created cards [`f89023e`](https://git.odit.services/lfk/frontend/commit/f89023e24ab30c90a8cacf2e9590d1dfa1a743d5)
- ScansOverview: fallback for manual scans [`1ec9556`](https://git.odit.services/lfk/frontend/commit/1ec9556aa64b996d0bdd9cd3ee50a767a154f5f6)
- fix min-w th [`b35375c`](https://git.odit.services/lfk/frontend/commit/b35375c929ad8d525b2761c321ecef0399917828)
- Close modal on delete and import toastify [`03b7ada`](https://git.odit.services/lfk/frontend/commit/03b7ada5ef3cbf64f178d3d0d88c448d76a42885)
- Fixed styling [`13254b2`](https://git.odit.services/lfk/frontend/commit/13254b24dd75ca3eb47fd53d5368f047545e8420)
- Implemented table buttons [`11a56f8`](https://git.odit.services/lfk/frontend/commit/11a56f87e89866bd9b2f7029ef683fd139d60aa4)
- RunnersOverview: disable debug log [`2c992a0`](https://git.odit.services/lfk/frontend/commit/2c992a0e63fa72055d28785fe7f5c21fd26f1fea)
- RunnersOverview: TopActionSection: add margin top [`88f96ac`](https://git.odit.services/lfk/frontend/commit/88f96acc3c31c748d3da29b4564dd681a89208b9)
- Unused filter function value [`38d3e19`](https://git.odit.services/lfk/frontend/commit/38d3e1912cd6d5362db236c34e0c602b52060b73)
- Make the linter happy [`fbc14fd`](https://git.odit.services/lfk/frontend/commit/fbc14fd7b47009bf64a8824e7b60373eea031e3f)
- Dsable column filter for distance [`67eff0e`](https://git.odit.services/lfk/frontend/commit/67eff0eda9b7d49d1abe784247afdbfbd7a76b00)
- getPaginationRowModel [`238082b`](https://git.odit.services/lfk/frontend/commit/238082b657998f513111e832556926decbe84e21)
- -&gt; onkeyup [`aecbabe`](https://git.odit.services/lfk/frontend/commit/aecbabe52221c0ab8d94a42dc8ac42f4f397fde8)
- removed unused import [`9811ede`](https://git.odit.services/lfk/frontend/commit/9811ede3b23a488a49962a43dae5abad60f93341)
- drop redundant button role [`e7eddb4`](https://git.odit.services/lfk/frontend/commit/e7eddb4f08632cc803b8752e3561319fab538843)
- Filter id as equals [`2699b06`](https://git.odit.services/lfk/frontend/commit/2699b06d7c42608b1b3ce2037908a78143038441)
- debug table [`fd0d45f`](https://git.odit.services/lfk/frontend/commit/fd0d45f721396787ad35fca2a7fd0f26d98b23e9)
- add TODO for ScansOverview status filter [`9505c2b`](https://git.odit.services/lfk/frontend/commit/9505c2b03005f4e667082cb4527fea863dbbf7fc)
#### [0.17.3](https://git.odit.services/lfk/frontend/compare/0.17.2...0.17.3)
> 15 March 2023
- dependency fixes [`3ea7a01`](https://git.odit.services/lfk/frontend/commit/3ea7a015a9beba3c2e4d3eb966f24ff6d4ac786e)
- 🚀RELEASE v0.17.3 [`85705b6`](https://git.odit.services/lfk/frontend/commit/85705b6e684d0c41d3dc770cef7bffb199101576)
- set pnpm to @7 [`4432941`](https://git.odit.services/lfk/frontend/commit/44329413ed2ca23f74e86db041b2c25b2b1c2a2b)
#### [0.17.2](https://git.odit.services/lfk/frontend/compare/0.17.1...0.17.2)
> 15 March 2023
- new license file version [CI SKIP] [`00359d2`](https://git.odit.services/lfk/frontend/commit/00359d25c1bd3efdd6365bf284b3c07634049399)
- 🚀RELEASE v0.17.2 [`46db68a`](https://git.odit.services/lfk/frontend/commit/46db68ab229dc740dfff8835ef916f2c2e629b27)
- improved ThFilterGroup style [`f917018`](https://git.odit.services/lfk/frontend/commit/f917018fd92a8a5b034f735ac8b6e41995044317)
#### [0.17.1](https://git.odit.services/lfk/frontend/compare/0.17.0...0.17.1)
> 15 March 2023
- Revert "package dependency fixes, bumps, lockfile update" [`d8a3063`](https://git.odit.services/lfk/frontend/commit/d8a30637351e164599e07a6473d9a1d2b08d245d)
- 🚀RELEASE v0.17.1 [`7b420c4`](https://git.odit.services/lfk/frontend/commit/7b420c430d27bf0fc85a4297780164a593fc1be3)
#### [0.17.0](https://git.odit.services/lfk/frontend/compare/0.16.5...0.17.0)
> 15 March 2023
- new license file version [CI SKIP] [`61328d2`](https://git.odit.services/lfk/frontend/commit/61328d20ed2cfd1df7d3c32767f9c64154879d6d)
- wip: pnpm + node version [`732b2f0`](https://git.odit.services/lfk/frontend/commit/732b2f061e465bd08cf34c58d8cd6b021ba25dce)
- package dependency fixes, bumps, lockfile update [`2c73b98`](https://git.odit.services/lfk/frontend/commit/2c73b9862d401dac15021eed3f7847d46132a8ed)
- Fixed runners not showing up [`75bc89c`](https://git.odit.services/lfk/frontend/commit/75bc89ca3020c48f490c7602374616bd9461e78f)
- add ThFilterRunner [`3a3e2f7`](https://git.odit.services/lfk/frontend/commit/3a3e2f71575d3a0e39a5e13b05cff932b8683ac9)
- fix styling for table filters th border [`bea57aa`](https://git.odit.services/lfk/frontend/commit/bea57aa03acaaaa4b1860b30228dd5d1298317f8)
- You can now create cards from runners by searching via #runnerid [`e8a0ad6`](https://git.odit.services/lfk/frontend/commit/e8a0ad6647ab39252865f62b755f27c34ac2d243)
- remodelled for early return [`b869b5f`](https://git.odit.services/lfk/frontend/commit/b869b5fd2a01955fb21f936fa38eb5a9648e7de3)
- 🚀RELEASE v0.17.0 [`6491af1`](https://git.odit.services/lfk/frontend/commit/6491af19e375cbeba7ddd94e463b4dfe308a70a8)
- Wow this api is fun [`32a9074`](https://git.odit.services/lfk/frontend/commit/32a9074963cce3328f14b1f981ddd5ee49df0008)
- Fixed double space in label [`92b89cc`](https://git.odit.services/lfk/frontend/commit/92b89cc4d88c9d5625c2ddf7c81c98494f7f5271)
- UsersOverview: drop pfp [`30991d5`](https://git.odit.services/lfk/frontend/commit/30991d5364a09d517b2115a7e9ea3fbf1fe2e57d)
- Switched license generation to cache registry and pnpm [`0a6d92a`](https://git.odit.services/lfk/frontend/commit/0a6d92a1f3c5562f11562c433b3a04e3eaae3da4)
- Pinned pnpm in dockerfile, thx @philipp [`3a576d1`](https://git.odit.services/lfk/frontend/commit/3a576d1073ee503b68100e01054a1756bab62805)
- Pinned ci node version [`b30b98b`](https://git.odit.services/lfk/frontend/commit/b30b98b521eda2bc7fc055097546f716e90d92ef)
- Fixed pnpm being called without being installed [`43d82a2`](https://git.odit.services/lfk/frontend/commit/43d82a2af04af49c2169f78a0d0f27ef7e4d7558)
- Merge pull request 'bugfix/162-create_card_modal' (#163) from bugfix/162-create_card_modal into dev [`6a4495b`](https://git.odit.services/lfk/frontend/commit/6a4495b8131a31cd48a608c2275e80494d0a0fb4)
- Removed unused log [`268b1b1`](https://git.odit.services/lfk/frontend/commit/268b1b1d9830de196d1d95345d7a2467bbf19eb6)
- Merge pull request 'filter by runner full names + "#&lt;ID&gt;"' (#160) from feature/159-cardsoverview-filter-for-runner-full-names-and-id into dev [`0625937`](https://git.odit.services/lfk/frontend/commit/0625937068f0786078ffd29b9c8bb54949350b6c)
- UsersOverview: change profilepic scaling [`5cc8b08`](https://git.odit.services/lfk/frontend/commit/5cc8b0811cf290f97a4399b23c5ea4d961a5a91c)
#### [0.16.5](https://git.odit.services/lfk/frontend/compare/0.16.4...0.16.5)
> 14 March 2023
- 🚀RELEASE v0.16.5 [`3680533`](https://git.odit.services/lfk/frontend/commit/3680533eefef042fc77246dd3d374aafe10c428f)
- new license file version [CI SKIP] [`405dfa0`](https://git.odit.services/lfk/frontend/commit/405dfa0c34ba87fc450c22e0e9974f92c4cdeffe)
#### [0.16.4](https://git.odit.services/lfk/frontend/compare/0.16.3...0.16.4)
> 14 March 2023
- fix: OrgDetail: clicking on address will toggle selfservice [`#158`](https://git.odit.services/lfk/frontend/issues/158)
- 🚀RELEASE v0.16.4 [`5c2d154`](https://git.odit.services/lfk/frontend/commit/5c2d154ad180ce7916605871c63e2f5ac4428250)
#### [0.16.3](https://git.odit.services/lfk/frontend/compare/0.16.2...0.16.3)
> 23 February 2023
- 🚀RELEASE v0.16.3 [`f9cfd6b`](https://git.odit.services/lfk/frontend/commit/f9cfd6bd063b01a584774854d8fb5eab96f99528)
- Bumped vite build targets [`5fe4763`](https://git.odit.services/lfk/frontend/commit/5fe47634e8980e77b65c05f213c475cf49273609)
- new license file version [CI SKIP] [`a659091`](https://git.odit.services/lfk/frontend/commit/a6590910cfdc5e91fba91c1bc9237e407ef15fd2)
- Merge pull request 'feature/156-pdf_names' (#157) from feature/156-pdf_names into dev [`ad454c3`](https://git.odit.services/lfk/frontend/commit/ad454c386cbf11abc59d41d269d1a0ef7442c9ed)
- Added ids for generated pdfs [`0b2c296`](https://git.odit.services/lfk/frontend/commit/0b2c296de069a4ef302c5535de01bc18236675bc)
#### [0.16.2](https://git.odit.services/lfk/frontend/compare/0.16.1...0.16.2)
> 23 February 2023
- Fixed scanmodal [`#154`](https://git.odit.services/lfk/frontend/issues/154)
- 🚀RELEASE v0.16.2 [`0e85940`](https://git.odit.services/lfk/frontend/commit/0e85940cba292cbccd1ec038aa24f4a719382c19)
- Merge pull request 'feature/147-cardoverview_performance' (#153) from feature/147-cardoverview_performance into dev [`8d479c3`](https://git.odit.services/lfk/frontend/commit/8d479c32f82938904aee6a10deb80fea85487e4b)
- Merge pull request 'Fixed scanmodal' (#155) from bugfix/154-scan_select_runner_by_id into dev [`549785c`](https://git.odit.services/lfk/frontend/commit/549785cf7d1c9edc1be37dc745b97096232a08ca)
- i18n [`ca6da15`](https://git.odit.services/lfk/frontend/commit/ca6da15ef761184a55b18d56f749f660a32cbb83)
- Bsic datatable conversion [`757655e`](https://git.odit.services/lfk/frontend/commit/757655ea63b3667bc4612ae1595eb52910b61137)
- 1st datatable try with @vincjo/datatables [`81b8fbf`](https://git.odit.services/lfk/frontend/commit/81b8fbf4e341e6f2998a6e9e2053972c5c225021)
- Dasboard Cards redesign [`eb1c17e`](https://git.odit.services/lfk/frontend/commit/eb1c17e3ac7e8f5e7310a90421fc9db3ed15c497)
- set .phone to null if empty [`da6dd55`](https://git.odit.services/lfk/frontend/commit/da6dd55d139a672fa50204eabdca67d9740614a0)
- add group filtering to table [`14d64b6`](https://git.odit.services/lfk/frontend/commit/14d64b6070d98e6368da5709e9ff8221e8a621c7)
- formatting [`24d0747`](https://git.odit.services/lfk/frontend/commit/24d074752f1c5dc1a14b075ac14b448d7e129376)
- RunnersOverview loading fix [`2e075ea`](https://git.odit.services/lfk/frontend/commit/2e075eafab5c4d78fd9aa9d66834b477b2685bfc)
- Added old formatting for runner and status [`df63c23`](https://git.odit.services/lfk/frontend/commit/df63c2388da359dec9ed9968bc9f970be7092a45)
- Added custom status filter [`f0a2b28`](https://git.odit.services/lfk/frontend/commit/f0a2b2859fa18426a454b7d9d6dd22dfdfe7ce27)
- Basic checkbox fix [`1337676`](https://git.odit.services/lfk/frontend/commit/1337676e0894c46da0b6dcb7553e5ea8f88d0c14)
- Fixed all filter [`8dfa19f`](https://git.odit.services/lfk/frontend/commit/8dfa19fa0f9897c61342ece956df88731c7aa861)
- improved runner search [`1b0cd5b`](https://git.odit.services/lfk/frontend/commit/1b0cd5b90bcceb92627c6b7cdcdd7792ed81b50f)
- set table-layout:fixed + display when loaded [`65e8998`](https://git.odit.services/lfk/frontend/commit/65e89988943807c1606a8b6aea49564b13d52537)
- Trigger edit modal [`32ddb66`](https://git.odit.services/lfk/frontend/commit/32ddb66fc8d8cd689f1104759812f4cee4b7a613)
- cleaned up table search [`08047a9`](https://git.odit.services/lfk/frontend/commit/08047a93073c32f5dd7a8e958400ae8a5b7f4035)
- Fixed edit update bug [`0feee0a`](https://git.odit.services/lfk/frontend/commit/0feee0ae2fb6d8dba0b6fd72cedc0712dc749511)
- fix: z-index on action buttons [`224034d`](https://git.odit.services/lfk/frontend/commit/224034dcc6263d3b0a8ea20045e435142d8ed2af)
- rename: ThFilterGroup -&gt; ThFilterStatus [`2a6a399`](https://git.odit.services/lfk/frontend/commit/2a6a39916a03c0466e63354e9f5ad7924cb59b6b)
- new license file version [CI SKIP] [`0e5490f`](https://git.odit.services/lfk/frontend/commit/0e5490f1c84217a5a6d5c8745c4667b32ca65e1a)
- new license file version [CI SKIP] [`026d3d4`](https://git.odit.services/lfk/frontend/commit/026d3d41c1b976a4dc7c733576a6a9e8d4b13b78)
- Updated breakpoints [`452d010`](https://git.odit.services/lfk/frontend/commit/452d0101838d72bff7d588a953faae028e2ff819)
- Tailwind bump [`a101873`](https://git.odit.services/lfk/frontend/commit/a101873eb0946b284a11a5081642711f5087da14)
- Fixed checkbox show [`0900c26`](https://git.odit.services/lfk/frontend/commit/0900c2691e4cfe5046e8ae186c8ac8884c90abd6)
- Removed unused console log [`aafc4c8`](https://git.odit.services/lfk/frontend/commit/aafc4c8d62a7a0a493c8bd60149f90c842534bdd)
- i18n import [`6fe134a`](https://git.odit.services/lfk/frontend/commit/6fe134afc8bfef4e7470b7e53b9312b172a7322b)
- Merge pull request 'fix: RunnerDetail: set .phone to null if empty' (#152) from bugfix/151-runnerdetail--cannot-unset-phone-number into dev [`329c1cc`](https://git.odit.services/lfk/frontend/commit/329c1cc037a43c818ba3b6c72581d29586d76232)
- Merge pull request 'feature/146-runner-table-performance-data-table' (#150) from feature/146-runner-table-performance-data-table into dev [`b82d638`](https://git.odit.services/lfk/frontend/commit/b82d638de1aa1f72aada212cf3e4147d808b4fcf)
- Merge pull request 'feature/148-dashboard_statscards' (#149) from feature/148-dashboard_statscards into dev [`fd1a06b`](https://git.odit.services/lfk/frontend/commit/fd1a06b3595b3713ad474e623c74105125602d46)
- Fixed top checkbox state [`3d2acb6`](https://git.odit.services/lfk/frontend/commit/3d2acb692a28c116790248679e238fb562b24ac5)
#### [0.16.1](https://git.odit.services/lfk/frontend/compare/0.16.0...0.16.1)
> 15 February 2023
- fix: donor detail: sponsorings: unset middlename will show as "null" [`#145`](https://git.odit.services/lfk/frontend/issues/145)
- 🚀RELEASE v0.16.1 [`4499480`](https://git.odit.services/lfk/frontend/commit/449948050b8673d43a8dfbb225c3198e4bbb3c7b)
#### [0.16.0](https://git.odit.services/lfk/frontend/compare/0.15.6...0.16.0)
> 3 February 2023
- First page for statsclients [`f299617`](https://git.odit.services/lfk/frontend/commit/f299617c600d2bba7b4405c7c3acae9fd93aefa8)
- 🚀RELEASE v0.16.0 [`75684ef`](https://git.odit.services/lfk/frontend/commit/75684efa1ae0edb4b4d414757c5acf2a77c572e5)
- Basic statsclient detail [`0215860`](https://git.odit.services/lfk/frontend/commit/02158605be824e5ac21a6284731138190988c794)
- Updated Add modal [`f679330`](https://git.odit.services/lfk/frontend/commit/f679330466205e6480cd7f2b7c2b4fdc41c51525)
- Switched drone to kaniko [`1c98005`](https://git.odit.services/lfk/frontend/commit/1c980059cff5c87c452428b53513507c2339451f)
- Re-added copy modal [`fecb07e`](https://git.odit.services/lfk/frontend/commit/fecb07ee373dcaaeaea69fdf8d4c6ee2c257c89c)
- Added Statsclients to sidebar [`068076d`](https://git.odit.services/lfk/frontend/commit/068076dd47373c673a25e730cb8a57c686682810)
- Fixed imports and naming [`f3cc07c`](https://git.odit.services/lfk/frontend/commit/f3cc07c009ed0a34e61f1aad47a1a31778145439)
- new license file version [CI SKIP] [`2c4f27a`](https://git.odit.services/lfk/frontend/commit/2c4f27a943bb35be6728bb49bd5c2263cba78165)
- Merge pull request 'feature/143-beamershow_clients' (#144) from feature/143-beamershow_clients into dev [`53b7dec`](https://git.odit.services/lfk/frontend/commit/53b7dec7cd516c908d45591b855f4be09371f9b1)
- Updated deletion modal [`93fc7c2`](https://git.odit.services/lfk/frontend/commit/93fc7c2e83f78dd88f15d9246127bb9e69f1a8ee)
- Updated mounted variables [`674e6a9`](https://git.odit.services/lfk/frontend/commit/674e6a90ec23dde9377bea64c14a50e41ffa450d)
- Removed Key after creation [`e10c648`](https://git.odit.services/lfk/frontend/commit/e10c6480a504338b21e30fdf2577e5b6c3b635db)
- Updated docker base images [`9767553`](https://git.odit.services/lfk/frontend/commit/976755338b8621064f9a73147aa600af1f77cd51)
- Added translation [`96c55db`](https://git.odit.services/lfk/frontend/commit/96c55db63dbfed92b78ff0e7bdab7a8cce4d76e9)
- Pinned versions [`cff112d`](https://git.odit.services/lfk/frontend/commit/cff112d705a74a135286943298f3f344341325ac)
- Tailwind bump [`e0cbfb0`](https://git.odit.services/lfk/frontend/commit/e0cbfb000bee59a71e06bd58a9c7ef6a0fc7091d)
- Added missing translation [`19a333d`](https://git.odit.services/lfk/frontend/commit/19a333d7bda525fbcb3c68f3cbf85a4f925a9707)
- Bumped apiclient [`c28f1ee`](https://git.odit.services/lfk/frontend/commit/c28f1ee0bc4456595c21858f38e52ed6f16871c5)
- new license file version [CI SKIP] [`3a66f4c`](https://git.odit.services/lfk/frontend/commit/3a66f4c862db9f35c223cc7007b0560fef4e1d63)
- Bumped apiclient [`28cbc5b`](https://git.odit.services/lfk/frontend/commit/28cbc5b98ca09657100e1740b83aa2617243b26b)
- Ignore pnpm lock [`2d8c4c1`](https://git.odit.services/lfk/frontend/commit/2d8c4c1698a1675f618e85e678012f310f87c6ee)
#### [0.15.6](https://git.odit.services/lfk/frontend/compare/0.15.5...0.15.6)
> 19 July 2021
- 🚀RELEASE v0.15.6 [`9fc4ad6`](https://git.odit.services/lfk/frontend/commit/9fc4ad63c4f77b46d645e83c94b51747b91247b8)
- Fixed donations getting reduced to the first one on certificates [`2391668`](https://git.odit.services/lfk/frontend/commit/2391668a25a1e11a1409df572d77ad1635070fbc)
- new license file version [CI SKIP] [`97054a7`](https://git.odit.services/lfk/frontend/commit/97054a71c1ab8a045762a55148124965c6994373)
#### [0.15.5](https://git.odit.services/lfk/frontend/compare/0.15.4...0.15.5)
> 5 July 2021
- 🚀RELEASE v0.15.5 [`717d335`](https://git.odit.services/lfk/frontend/commit/717d33547c3378424dd720005da9952f8a753f1a)
- Merge pull request 'Fixed kilometer conversion' (#142) from bugfix/141-runner_kilometers into dev [`997be32`](https://git.odit.services/lfk/frontend/commit/997be32679dc38c9fb0e92b6ce011057b854d99d)
- Fixed kilometer conversion [`134f00c`](https://git.odit.services/lfk/frontend/commit/134f00c40e0c8252e7604a73151e8d6685b2c61d)
- new license file version [CI SKIP] [`e752ee1`](https://git.odit.services/lfk/frontend/commit/e752ee12d17a4423f4364f7766eafbe7d4cef2d1)
#### [0.15.4](https://git.odit.services/lfk/frontend/compare/0.15.3...0.15.4)
> 5 July 2021
- Merge pull request 'fix total donation sum in dashboard' (#140) from bugfix/139-total-donation-sum-is-wrong into dev [`#139`](https://git.odit.services/lfk/frontend/issues/139)
- 🚀RELEASE v0.15.4 [`cc4515f`](https://git.odit.services/lfk/frontend/commit/cc4515ff66b1c1de3747d0ee6cc465574accedb7)
- divide by 100 + toFixes(2) [`b246f2b`](https://git.odit.services/lfk/frontend/commit/b246f2b349b06d1adea318dfad58f97fb1a249bb)
#### [0.15.3](https://git.odit.services/lfk/frontend/compare/0.15.2...0.15.3)
> 16 April 2021
- 🚀RELEASE v0.15.3 [`76b69d8`](https://git.odit.services/lfk/frontend/commit/76b69d851aa590ecf8caac135b72962a72e83635)
- Small bugfix (null got displayed) 🛠 [`224f586`](https://git.odit.services/lfk/frontend/commit/224f5863683ae2543a4a435510ed2c558dc5d307)
#### [0.15.2](https://git.odit.services/lfk/frontend/compare/0.15.1...0.15.2)
> 16 April 2021
- 🚀RELEASE v0.15.2 [`9add6c8`](https://git.odit.services/lfk/frontend/commit/9add6c8ff1fbeed91fe97a7cf262921b716f4e3c)
- Footer - noopener link [`cee04c1`](https://git.odit.services/lfk/frontend/commit/cee04c1d6fb6005cefe77fb95855ab6fe2cc448f)
- Hotfix: Team change recognition 🐞 [`cbec785`](https://git.odit.services/lfk/frontend/commit/cbec78589d2fa21f12ce87e71bff2b49c3a7d345)
- NGINX cache assets [`e54a480`](https://git.odit.services/lfk/frontend/commit/e54a4807f70bc333396885f81d3dcc7ae6c115d9)
#### [0.15.1](https://git.odit.services/lfk/frontend/compare/0.15.0...0.15.1)
> 16 April 2021
- 🚀RELEASE v0.15.1 [`a85db7c`](https://git.odit.services/lfk/frontend/commit/a85db7cb3f89881794e37a66ecd822f8ad5873f1)
- Merge pull request '🐞🐳 fix Dockerfile' (#138) from bugfix/136-opacity_reactivity into dev [`2bd3779`](https://git.odit.services/lfk/frontend/commit/2bd3779839de16a89b91a3da93033e2a2b742ab7)
- 🚚 move to tailwind [`07ac041`](https://git.odit.services/lfk/frontend/commit/07ac041d69b3b1810e5db538b53fe62084490f7a)
- 🐞🐳 fix Dockerfile [`303e33c`](https://git.odit.services/lfk/frontend/commit/303e33cafb4a1be01e4c4b43f46ff0c651cb4620)
- Dockerfile now uses selfhosted registry [`b4e689d`](https://git.odit.services/lfk/frontend/commit/b4e689dddf0b93a2794aa30ea83e8c6505d6bbfd)
- new license file version [CI SKIP] [`98a0b03`](https://git.odit.services/lfk/frontend/commit/98a0b036c5490b4bc4992e83f3bca02be39927fa)
- Merge pull request 'Opacity import fix bugfix/136-opacity_reactivity' (#137) from bugfix/136-opacity_reactivity into dev [`fb3f30f`](https://git.odit.services/lfk/frontend/commit/fb3f30fb1024de61ce1c541dae90374454f6ef96)
- Added bs import fix [`6213952`](https://git.odit.services/lfk/frontend/commit/621395200751c2d42b9ad44c77e84bda03b62e83)
#### [0.15.0](https://git.odit.services/lfk/frontend/compare/0.14.0...0.15.0)
> 15 April 2021
- 🚀RELEASE v0.15.0 [`5c02028`](https://git.odit.services/lfk/frontend/commit/5c02028841c68d9a284bf6971eec2b6bc2fdf1f3)
- Merge pull request 'Mark donations as payed feature/133-donation_payments' (#135) from feature/133-donation_payments into dev [`c561b53`](https://git.odit.services/lfk/frontend/commit/c561b536705a68215d9c0a6b320587d1647bf57f)
- Sorted translations [`c7a858e`](https://git.odit.services/lfk/frontend/commit/c7a858eed7962294bc9df3c92ce2e46b0a354796)
- Added total donation amount to donor overview [`e42ea94`](https://git.odit.services/lfk/frontend/commit/e42ea943b7821d433fe21599edbd9f76c3128ef2)
- Added Add Payment button to donor overview [`6e6e8b2`](https://git.odit.services/lfk/frontend/commit/6e6e8b26171f16542c101520800b4b6ea7c023d3)
- You can now open a modal to add a payment to a donation from the donation overview [`a943aaf`](https://git.odit.services/lfk/frontend/commit/a943aaf5fce8f113dd967d3842e2b0d7d50604e9)
- You can now add payments from the donation overview [`1dbab03`](https://git.odit.services/lfk/frontend/commit/1dbab03fe73b5e0fc011f9b0af7199bd71bc79c5)
- Added payment updating via detail [`bdcf5d3`](https://git.odit.services/lfk/frontend/commit/bdcf5d3fc08d250377226a253642d79b2e82d624)
- Added **all** missing toast translations [`de5aa92`](https://git.odit.services/lfk/frontend/commit/de5aa9237d261b5d47a8def35afa7f8e0089aea6)
- You can now mark fixed donations as already paid on creation [`3d3a10a`](https://git.odit.services/lfk/frontend/commit/3d3a10aafb16d371be9471eb5172f9251fb2583f)
- Added translations 🌎 [`d015f97`](https://git.odit.services/lfk/frontend/commit/d015f9739570c44a7a2fe6ba248c9a45c3047c62)
- Changed top info style for donation overview [`4c2c24a`](https://git.odit.services/lfk/frontend/commit/4c2c24af2ca5c2874a583b0fd93bee147a17f449)
- Added paid donation amount and status to donation detail [`5645eea`](https://git.odit.services/lfk/frontend/commit/5645eeaafaa4254edf1a81bc597ce0c7a9b03ff0)
- Added total donation amount to donation overview [`961477d`](https://git.odit.services/lfk/frontend/commit/961477d5224bc44b552d2fc2851d8514116f4e20)
- Fixed chante recognition bug for fixed donation [`0f0aae7`](https://git.odit.services/lfk/frontend/commit/0f0aae7ba4cf5dfab15d56ce48edbdbc7cb7e403)
- Added total donation amount to donor detail [`a5f7101`](https://git.odit.services/lfk/frontend/commit/a5f71015a6557d664e9d3f505613352792fc38cb)
- Added msiisng runner id conversion [`5761815`](https://git.odit.services/lfk/frontend/commit/57618156b49b2b0f0274f2126fef36a017d90022)
- AddDonationModal - vertical alignment for paid status [`18acac8`](https://git.odit.services/lfk/frontend/commit/18acac83bc6532e14d36b3399d867e026d0c88ac)
- Added missing updated comparison [`04a3038`](https://git.odit.services/lfk/frontend/commit/04a3038369f2717c43459318b7b5754ebbaa9e45)
- DonationsOverview contrast on action [`d7d4447`](https://git.odit.services/lfk/frontend/commit/d7d44470bb08ac06594bc400608c17eeacb0434b)
- Fixed typo [`4c0886a`](https://git.odit.services/lfk/frontend/commit/4c0886a5d9b91439967bc8f66b09a57177f967d0)
- Fixed styling [`865254d`](https://git.odit.services/lfk/frontend/commit/865254d646b5f7de15720551c67ae649601cbcd2)
- Changed top info style for donation detail [`000fc97`](https://git.odit.services/lfk/frontend/commit/000fc97beb14427f69d421ff2c96975dbbdc7a3a)
#### [0.14.0](https://git.odit.services/lfk/frontend/compare/0.13.1...0.14.0)
> 14 April 2021
- Merge pull request 'added donor receipt list download to DonorsOverview' (#134) from feature/132-export-donors-receipt-list into dev [`#132`](https://git.odit.services/lfk/frontend/issues/132)
- Sorted translations 🌎 [`c6c9751`](https://git.odit.services/lfk/frontend/commit/c6c97516b3981ef580d620c0c8a6fcc42f26facd)
- Fixed typos in translations [`03676b2`](https://git.odit.services/lfk/frontend/commit/03676b2894892c3559118b93e969c063b53b081e)
- added donor receipt list download to DonorsOverview [`d241ca5`](https://git.odit.services/lfk/frontend/commit/d241ca569838abbe9581fbd319f7f3b563cb7dcc)
- 🚀RELEASE v0.14.0 [`9c5fc6b`](https://git.odit.services/lfk/frontend/commit/9c5fc6b61c0bb2a6d831d4a23ef8679c6e68c6a1)
- ⏫ general version bump [`18f151c`](https://git.odit.services/lfk/frontend/commit/18f151c1fb878a74c3d1a2c2a2debf7913739417)
- new license file version [CI SKIP] [`302caf0`](https://git.odit.services/lfk/frontend/commit/302caf015f88f77e2b2ae2b67680e79f987ad81e)
- Switched to selfhosted images [`112eb29`](https://git.odit.services/lfk/frontend/commit/112eb29f932cd936f1d6c2308dcaeaf8cb642490)
- ⏫ bump @odit/lfk-client-js@0.11.0 [`9ca57fa`](https://git.odit.services/lfk/frontend/commit/9ca57fac2eeabbf25142a507fb9c0fa3c90b4e74)
- replace donationAmount with paidDonationAmount [`e90e56d`](https://git.odit.services/lfk/frontend/commit/e90e56d8b26aef23aba2bbb0c3942ba4d7feb224)
#### [0.13.1](https://git.odit.services/lfk/frontend/compare/0.13.0...0.13.1)
> 11 April 2021
- 🚀RELEASE v0.13.1 [`b512cf8`](https://git.odit.services/lfk/frontend/commit/b512cf86674f1c60b5ac790985ededdfd6554185)
- For await fix [`a24d292`](https://git.odit.services/lfk/frontend/commit/a24d2923c6e6da90d610c05183d29d47eaf2ed30)
#### [0.13.0](https://git.odit.services/lfk/frontend/compare/0.12.5...0.13.0)
> 11 April 2021
- 🚀RELEASE v0.13.0 [`467808a`](https://git.odit.services/lfk/frontend/commit/467808abefe127dac66a2837fcce3197dddb140f)
- Merge pull request 'Better org pdf generation feature/130-org_doc_splitting' (#131) from feature/130-org_doc_splitting into dev [`861f1f2`](https://git.odit.services/lfk/frontend/commit/861f1f221653283e7586aa2c67b205337fd44398)
- Org card generation now runs in sequence [`fef14b6`](https://git.odit.services/lfk/frontend/commit/fef14b6e4fb47ad92da61de91fedce96aea26b2c)
- Org certificate generation now runs in sequence [`509b22b`](https://git.odit.services/lfk/frontend/commit/509b22bea0dd3e4446e6ecc37d27644e9bf2ad50)
- Org contract generation now runs in sequence [`01d2a7e`](https://git.odit.services/lfk/frontend/commit/01d2a7e6aa709b3f2d71575f705fc962e97e2742)
- Emergency document server url change [`5476808`](https://git.odit.services/lfk/frontend/commit/5476808683a919bc34dbaea1f1ed276d49750096)
- Fixed const -&gt; let [`7447b2f`](https://git.odit.services/lfk/frontend/commit/7447b2f4c134a585905db6733093eab13e6f7c47)
- Hotfix: Org * generation🐞 [`ac586fe`](https://git.odit.services/lfk/frontend/commit/ac586fec5abd324d590ba99cdfe8ddddefbf95e6)
#### [0.12.5](https://git.odit.services/lfk/frontend/compare/0.12.4...0.12.5)
> 8 April 2021
- 🚀RELEASE v0.12.5 [`331d737`](https://git.odit.services/lfk/frontend/commit/331d737796c82454b1c19fa1840ccc20e36d2626)
- Merge pull request 'Added runner team's parentorg name to runenr overciew' (#129) from feature/128-runner_orgs into dev [`ef81b8a`](https://git.odit.services/lfk/frontend/commit/ef81b8adf9bef685a55936d7544bf645c0d6ecbe)
- Switched to html entity [`8a7d635`](https://git.odit.services/lfk/frontend/commit/8a7d635cef2d465e70c84e1f7a7b90b98a8dbab1)
- Added runner team's parentorg name to runenr overciew [`4c259c1`](https://git.odit.services/lfk/frontend/commit/4c259c1eef2b0166ce6a8493d0c9e9d5ede11146)
#### [0.12.4](https://git.odit.services/lfk/frontend/compare/0.12.3...0.12.4)
> 8 April 2021
- 🚀RELEASE v0.12.4 [`5b4ede5`](https://git.odit.services/lfk/frontend/commit/5b4ede5e2f6a26b475a7a4b430a4146d21fb9671)
- 🚑 [HOTFIX] - drop "svelte-infinite-loading" [`d0ab3dd`](https://git.odit.services/lfk/frontend/commit/d0ab3dda78bbad2cea18a2491056530897d56607)
#### [0.12.3](https://git.odit.services/lfk/frontend/compare/0.12.2...0.12.3)
@@ -1935,7 +1058,7 @@ All notable changes to this project will be documented in this file. Dates are d
- init [`32357ec`](https://git.odit.services/lfk/frontend/commit/32357ece0a7195ea1135c9c3e4c6c84323f95b4d)
- tmp [`1b7173c`](https://git.odit.services/lfk/frontend/commit/1b7173cda9134ee8058a00bdc030defa80d46bfc)
- Login - move to env.js import [`8ef0b21`](https://git.odit.services/lfk/frontend/commit/8ef0b21819309752c573d0485f6514152fb684e6)
- initial commit [`4bb3bae`](https://git.odit.services/lfk/frontend/commit/4bb3bae4e6fc89c35a8a2b36b7cd6e6d47958eae)
- initial commit [`4bb3bae`](https://git.odit.services/lfk/frontend/commit/4bb3bae4e6fc89c35a8a2b36b7cd6e6d47958eae)
- Initial license export [`4c96b9a`](https://git.odit.services/lfk/frontend/commit/4c96b9a3e04dbb7c021c71aa8828a29248509fbe)
- 🚚 move to tinro svelte router [`a50ea15`](https://git.odit.services/lfk/frontend/commit/a50ea15b38023b867a9f7757e973184cbcdd2457)
- new Dashboard [`7270ce9`](https://git.odit.services/lfk/frontend/commit/7270ce9d32869abd4f6ac65ab7c2c87363633cbe)
+9 -11
View File
@@ -1,16 +1,14 @@
FROM registry.odit.services/hub/library/node:23.10.0-alpine3.21 AS build
ARG NPM_REGISTRY_URL=https://registry.npmjs.org
FROM node:15.5.1-alpine3.12
WORKDIR /app
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 package.json ./
RUN yarn
COPY package.json *.config.js index.html ./
COPY src ./src
COPY public ./public
RUN pnpm build
RUN yarn build
# final image
FROM registry.odit.services/library/nginx-brotli:3.15 AS final
COPY --from=build /app/dist /usr/share/nginx/html
FROM alpine
COPY --from=0 /app/dist /app
FROM fholzer/nginx-brotli:v1.19.1
COPY --from=1 /app /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf
+6 -11
View File
@@ -1,27 +1,22 @@
# @odit/lfk-frontend
## ✒️ Overview
This is an API client for [https://git.odit.services/lfk/backend](@lfk/backend)
- WebApp built with [Svelte](https://svelte.dev), [WindiCSS](https://windicss.org/) (to compile [TailwindCSS](https://tailwindcss.com/)) and [Vite](https://vitejs.dev).
This application is intended for use by admin users/ members only.
## 🚀 Getting Started
```
pnpm i
yarn
```
## Development
```
pnpm dev
yarn dev
/
pnpm dev --open
yarn dev --open
```
## Build
```
pnpm build
```
yarn build
```
+6 -6
View File
@@ -1,8 +1,8 @@
version: "3"
services:
httpd:
build: .
volumes:
- ./public/env.sample.js:/usr/share/nginx/html/env.js
ports:
- 4050:80
httpd:
build: .
volumes:
- ./public/env.sample.js:/usr/share/nginx/html/env.js
ports:
- 4050:80
+22 -22
View File
@@ -1,22 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href="/lfk-logo.png" />
<meta name="theme-color" content="#FFFFFF" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Lauf Für Kaya! - Admin" />
<title>Lauf für Kaya! - Admin</title>
</head>
<body>
<span style="display: none; visibility: hidden" id="buildinfo"
>RELEASE_INFO-1.9.11-RELEASE_INFO</span
>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/lfk-logo.png">
<meta name="theme-color" content="#FFFFFF">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Lauf Für Kaya! - Admin" />
<title>Lauf für Kaya! - Admin</title>
</head>
<body>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.12.4-RELEASE_INFO</span>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>
-5
View File
@@ -6,11 +6,6 @@ http {
server {
error_page 404 /index.html;
root /usr/share/nginx/html;
location /assets {
expires 1y;
log_not_found off;
access_log off;
}
location = /index.html {
add_header Cache-Control 'no-store';
}
+13 -15
View File
@@ -1,18 +1,16 @@
import fs from "fs";
const fs = require('fs');
// get all language files
const files = fs.readdirSync("./src/locales/");
const files = fs.readdirSync('./src/locales/');
files.forEach((f) => {
// read file as object
const unordered = JSON.parse(fs.readFileSync(`src/locales/${f}`));
// order object by keys alpabetically A-Z
const ordered = Object.keys(unordered)
.sort()
.reduce((obj, key) => {
obj[key] = unordered[key];
return obj;
}, {});
// format output as json for commit diff compatibility
const out = JSON.stringify(ordered, 0, 4);
// write output file
fs.writeFileSync(`src/locales/${f}`, out);
// read file as object
const unordered = JSON.parse(fs.readFileSync(`src/locales/${f}`));
// order object by keys alpabetically A-Z
const ordered = Object.keys(unordered).sort().reduce((obj, key) => {
obj[key] = unordered[key];
return obj;
}, {});
// format output as json for commit diff compatibility
const out = JSON.stringify(ordered, 0, 4);
// write output file
fs.writeFileSync(`src/locales/${f}`, out);
});
+56 -65
View File
@@ -1,65 +1,56 @@
{
"name": "@odit/lfk-frontend",
"version": "1.9.11",
"type": "module",
"scripts": {
"i18n-order": "node order.js",
"dev": "vite",
"format": "prettier --write --plugin-search-dir=. .",
"build": "vite build",
"release": "release-it",
"licenses:export": "license-exporter --json -o public"
},
"license": "CC-BY-NC-SA-4.0",
"devDependencies": {
"@odit/license-exporter": "0.2.0",
"@sveltejs/vite-plugin-svelte": "2.1.1",
"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.4.15",
"vite": "4.3.3"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "chore(release): ${version}",
"push": true,
"tag": true,
"tagName": "${version}",
"tagAnnotation": "${version}"
},
"npm": {
"publish": false
},
"hooks": {
"after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js && git add index.html && node order.js && git add src/locales"
}
},
"dependencies": {
"@fontsource/athiti": "^5.2.5",
"@odit/lfk-client-js": "1.2.2",
"@paralleldrive/cuid2": "2.2.2",
"@tanstack/svelte-table": "8.9.1",
"bwip-js": "3.4.0",
"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.2.0",
"svelte-i18n": "3.6.0",
"tinro": "0.6.12",
"validator": "13.15.0",
"xlsx": "0.18.5"
},
"volta": {
"node": "20.0.0"
}
}
{
"name": "@odit/lfk-frontend",
"version": "0.12.4",
"scripts": {
"i18n-order": "node order.js",
"dev": "vite",
"build": "vite build",
"release": "release-it",
"licenses:export": "license-exporter --json -o public"
},
"license": "CC-BY-NC-SA-4.0",
"devDependencies": {
"check-password-strength": "2.0.2",
"@odit/lfk-client-js": "0.10.1",
"@odit/license-exporter": "0.0.11",
"@sveltejs/vite-plugin-svelte": "1.0.0-next.6",
"@types/html-minifier": "4.0.0",
"auto-changelog": "2.2.1",
"autoprefixer": "10.2.5",
"csvtojson": "2.0.10",
"gridjs": "3.4.0",
"html-minifier": "4.0.0",
"localforage": "1.9.0",
"marked": "2.0.1",
"release-it": "14.5.1",
"svelte": "3.37.0",
"svelte-focus-trap": "1.2.0",
"svelte-i18n": "3.3.9",
"svelte-preprocess": "4.7.0",
"svelte-select": "3.17.0",
"tailwindcss": "2.1.1",
"tinro": "0.6.1",
"toastify-js": "1.10.0",
"validator": "13.5.2",
"vite": "2.1.5",
"vite-plugin-windicss": "0.12.5",
"xlsx": "0.16.9"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀RELEASE v${version}",
"push": false,
"tag": true,
"tagName": null,
"tagAnnotation": "v${version}"
},
"npm": {
"publish": false
},
"hooks": {
"after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js && git add index.html && node order.js && git add src/locales"
}
}
}
-3761
View File
File diff suppressed because it is too large. Load diff
-3
View File
@@ -1,3 +0,0 @@
onlyBuiltDependencies:
- es5-ext
- esbuild
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
+6 -10
View File
@@ -1,12 +1,8 @@
const config = {
baseurl: "http://localhost:4010",
baseurl_selfservice: "http://localhost:5174",
baseurl_documentserver: "http://localhost:4010/documents",
documentserver_key:
"NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe",
// optional
default_username: "demo",
default_password: "demo",
prefersHashRouting: true,
baseurl: 'http://localhost:4010',
documentserver_key: 'NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe',
// optional
default_username: 'demo',
default_password: 'demo',
prefersHashRouting: true
};
window.config = config;
+1 -265
View File
@@ -1,265 +1 @@
[
{
"author": "ODIT.Services",
"repo": {
"type": "git",
"url": "git+https://git.odit.services/lfk/lfk-client-js"
},
"description": "A lib to interact with https://git.odit.services/lfk/backend. Use this version for native JS applications.",
"name": "@odit/lfk-client-js",
"license": "CC-BY-NC-SA-4.0",
"version": "1.1.2",
"licensetext": "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Creative\r\nCommons Corporation (\"Creative Commons\") is not a law firm and does not provide\r\nlegal services or legal advice. Distribution of Creative Commons public licenses\r\ndoes not create a lawyer-client or other relationship. Creative Commons makes\r\nits licenses and related information available on an \"as-is\" basis. Creative\r\nCommons gives no warranties regarding its licenses, any material licensed\r\nunder their terms and conditions, or any related information. Creative Commons\r\ndisclaims all liability for damages resulting from their use to the fullest\r\nextent possible.\r\n\r\nUsing Creative Commons Public Licenses\r\n\r\nCreative Commons public licenses provide a standard set of terms and conditions\r\nthat creators and other rights holders may use to share original works of\r\nauthorship and other material subject to copyright and certain other rights\r\nspecified in the public license below. The following considerations are for\r\ninformational purposes only, are not exhaustive, and do not form part of our\r\nlicenses.\r\n\r\nConsiderations for licensors: Our public licenses are intended for use by\r\nthose authorized to give the public permission to use material in ways otherwise\r\nrestricted by copyright and certain other rights. Our licenses are irrevocable.\r\nLicensors should read and understand the terms and conditions of the license\r\nthey choose before applying it. Licensors should also secure all rights necessary\r\nbefore applying our licenses so that the public can reuse the material as\r\nexpected. Licensors should clearly mark any material not subject to the license.\r\nThis includes other CC-licensed material, or material used under an exception\r\nor limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors\r\n\r\nConsiderations for the public: By using one of our public licenses, a licensor\r\ngrants the public permission to use the licensed material under specified\r\nterms and conditions. If the licensor's permission is not necessary for any\r\nreason–for example, because of any applicable exception or limitation to copyright–then\r\nthat use is not regulated by the license. Our licenses grant only permissions\r\nunder copyright and certain other rights that a licensor has authority to\r\ngrant. Use of the licensed material may still be restricted for other reasons,\r\nincluding because others have copyright or other rights in the material. A\r\nlicensor may make special requests, such as asking that all changes be marked\r\nor described. Although not required by our licenses, you are encouraged to\r\nrespect those requests where reasonable. More considerations for the public\r\n: wiki.creativecommons.org/Considerations_for_licensees\r\n\r\nCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public\r\nLicense\r\n\r\nBy exercising the Licensed Rights (defined below), You accept and agree to\r\nbe bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike\r\n4.0 International Public License (\"Public License\"). To the extent this Public\r\nLicense may be interpreted as a contract, You are granted the Licensed Rights\r\nin consideration of Your acceptance of these terms and conditions, and the\r\nLicensor grants You such rights in consideration of benefits the Licensor\r\nreceives from making the Licensed Material available under these terms and\r\nconditions.\r\n\r\nSection 1 – Definitions.\r\n\r\na. Adapted Material means material subject to Copyright and Similar Rights\r\nthat is derived from or based upon the Licensed Material and in which the\r\nLicensed Material is translated, altered, arranged, transformed, or otherwise\r\nmodified in a manner requiring permission under the Copyright and Similar\r\nRights held by the Licensor. For purposes of this Public License, where the\r\nLicensed Material is a musical work, performance, or sound recording, Adapted\r\nMaterial is always produced where the Licensed Material is synched in timed\r\nrelation with a moving image.\r\n\r\nb. Adapter's License means the license You apply to Your Copyright and Similar\r\nRights in Your contributions to Adapted Material in accordance with the terms\r\nand conditions of this Public License.\r\n\r\nc. BY-NC-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses,\r\napproved by Creative Commons as essentially the equivalent of this Public\r\nLicense.\r\n\r\nd. Copyright and Similar Rights means copyright and/or similar rights closely\r\nrelated to copyright including, without limitation, performance, broadcast,\r\nsound recording, and Sui Generis Database Rights, without regard to how the\r\nrights are labeled or categorized. For purposes of this Public License, the\r\nrights specified in Section 2(b)(1)-(2) are not Copyright and Similar RightLine truncated
},
{
"author": "Eric Elliott",
"repo": { "type": "git", "url": "https://github.com/ericelliott/cuid2" },
"description": "",
"name": "@paralleldrive/cuid2",
"license": "MIT",
"version": "2.2.0",
"licensetext": "MIT License\n\nCopyright (c) 2022 Eric Elliott\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"author": "Tanner Linsley",
"repo": { "type": "git", "url": "https://github.com/tanstack/table" },
"description": "Headless UI for building powerful tables & datagrids for Svelte.",
"name": "@tanstack/svelte-table",
"license": "MIT",
"version": "8.9.1",
"licensetext": ""
},
{
"author": "Mark Warren <mwarren@metafloor.com>",
"repo": { "type": "git", "url": "https://github.com/metafloor/bwip-js" },
"description": "JavaScript barcode generator supporting over 100 types and standards.",
"name": "bwip-js",
"license": "MIT",
"version": "3.4.0",
"licensetext": "bwip-js : Barcode Writer in Pure JavaScript\n\nCopyright (c) 2011-2019 Mark Warren\n\nThe MIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n"
},
{
"author": "deanilvincent",
"repo": {
"type": "git",
"url": "https://github.com/deanilvincent/check-password-strength"
},
"description": "A NPM Password strength checker based from Javascript RegExp. Check passphrase if it's \"Too weak\", \"Weak\", \"Medium\" or \"Strong\"",
"name": "check-password-strength",
"license": "MIT",
"version": "2.0.7",
"licensetext": "MIT License\r\n\r\nCopyright (c) 2020 Mark Deanil Vicente\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
},
{
"author": "Keyang Xiang <keyang.xiang@gmail.com>",
"repo": {
"type": "git",
"url": "https://github.com/Keyang/node-csvtojson"
},
"description": "A tool concentrating on converting csv data to JSON with customised parser supporting",
"name": "csvtojson",
"license": "MIT",
"version": "2.0.10",
"licensetext": "Copyright (C) 2013 Keyang Xiang\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
{
"author": "Mozilla",
"repo": {
"type": "git",
"url": "https://github.comlocalForage/localForage"
},
"description": "Offline storage, improved.",
"name": "localforage",
"license": "Apache-2.0",
"version": "1.10.0",
"licensetext": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under thiLine truncated
},
{
"author": "Christopher Jeffrey",
"repo": { "url": "git://github.com/markedjs/marked" },
"description": "A markdown parser built for speed",
"name": "marked",
"license": "MIT",
"version": "4.3.0",
"licensetext": "# License information\n\n## Contribution License Agreement\n\nIf you contribute code to this project, you are implicitly allowing your code\nto be distributed under the MIT license. You are also implicitly verifying that\nall code is your original work. `</legalese>`\n\n## Marked\n\nCopyright (c) 2018+, MarkedJS (https://github.com/markedjs/)\nCopyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n## Markdown\n\nCopyright © 2004, John Gruber\nhttp://daringfireball.net/\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n* Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nThis software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.\n"
},
{
"author": "Rich Harris",
"repo": { "type": "git", "url": "https://github.com/sveltejs/svelte" },
"description": "Cybernetically enhanced web apps",
"name": "svelte",
"license": "MIT",
"version": "3.58.0",
"licensetext": "Copyright (c) 2016-23 [these people](https://github.com/sveltejs/svelte/graphs/contributors)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
{
"author": "Kabir Goel",
"repo": {
"type": "git",
"url": "https://github.com/kbrgl/svelte-french-toast"
},
"description": "Buttery smooth Svelte toasts. Lightweight, customizable, and beautiful by default.",
"name": "svelte-french-toast",
"license": "MIT",
"version": "1.0.4-beta.0",
"licensetext": "MIT License\n\nCopyright (c) 2022 Kabir Goel\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"author": "Christian Kaisermann <christian@kaisermann.me>",
"repo": { "url": "https://github.com/kaisermann/svelte-i18n" },
"description": "Internationalization library for Svelte",
"name": "svelte-i18n",
"license": "MIT",
"version": "3.6.0",
"licensetext": "Copyright 2017 Christian Kaisermann <christian@kaisermann.me>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
{
"author": "Alexey Schebelev",
"repo": { "type": "git", "url": "https://github.com/AlexxNB/tinro" },
"description": "tinro is a tiny declarative router for Svelte",
"name": "tinro",
"license": "MIT",
"version": "0.6.12",
"licensetext": "MIT License\n\nCopyright (c) 2020 Alexey Schebelev\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"author": "Chris O'Hara <cohara87@gmail.com>",
"repo": {
"type": "git",
"url": "https://github.com/validatorjs/validator.js"
},
"description": "String validation and sanitization",
"name": "validator",
"license": "MIT",
"version": "13.9.0",
"licensetext": "Copyright (c) 2018 Chris O'Hara <cohara87@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
{
"author": "sheetjs",
"repo": { "type": "git", "url": "https://github.comSheetJS/sheetjs" },
"description": "SheetJS Spreadsheet data parser and writer",
"name": "xlsx",
"license": "Apache-2.0",
"version": "0.18.5",
"licensetext": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under thiLine truncated
},
{
"author": "ODIT.Services",
"repo": {
"type": "git",
"url": "https://git.odit.services/odit/license-exporter"
},
"description": "A simple license crawler for crediting open source work",
"name": "@odit/license-exporter",
"license": "MIT",
"version": "0.2.0",
"licensetext": "MIT License Copyright (c) 2020-2022 ODIT.Services (info@odit.services)\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is furnished\r\nto do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice (including the next\r\nparagraph) shall be included in all copies or substantial portions of the\r\nSoftware.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\r\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS\r\nOR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF\r\nOR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n"
},
{
"author": "dominikg",
"repo": {
"type": "git",
"url": "https://github.com/sveltejs/vite-plugin-svelte",
"directory": "packages/vite-plugin-svelte"
},
"description": "",
"name": "@sveltejs/vite-plugin-svelte",
"license": "MIT",
"version": "2.1.1",
"licensetext": "MIT License\n\nCopyright (c) 2021 [these people](https://github.com/sveltejs/vite-plugin-svelte/graphs/contributors)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"author": "Pete Cook <pete@cookpete.com> (https://github.com/cookpete)",
"repo": {
"type": "git",
"url": "https://github.com/CookPete/auto-changelog"
},
"description": "Command line tool for generating a changelog from git tags and commit history",
"name": "auto-changelog",
"license": "MIT",
"version": "2.4.0",
"licensetext": "The MIT License\n\nCopyright (c) 2017 Pete Cook https://cookpete.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
},
{
"author": "Andrey Sitnik <andrey@sitnik.ru>",
"repo": { "url": "postcss/autoprefixer" },
"description": "Parse CSS and add vendor prefixes to CSS rules using values from the Can I Use website",
"name": "autoprefixer",
"license": "MIT",
"version": "10.4.14",
"licensetext": "The MIT License (MIT)\n\nCopyright 2013 Andrey Sitnik <andrey@sitnik.ru>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
{
"author": "Andrey Sitnik <andrey@sitnik.ru>",
"repo": { "url": "postcss/postcss" },
"description": "Tool for transforming styles with JS plugins",
"name": "postcss",
"license": "MIT",
"version": "8.4.23",
"licensetext": "The MIT License (MIT)\n\nCopyright 2013 Andrey Sitnik <andrey@sitnik.ru>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
{
"author": "James Long",
"repo": { "url": "prettier/prettier" },
"description": "Prettier is an opinionated code formatter",
"name": "prettier",
"license": "MIT",
"version": "2.8.8",
"licensetext": "# Prettier license\n\nPrettier is released under the MIT license:\n\nCopyright © James Long and contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n## Licenses of bundled dependencies\n\nThe published Prettier artifact additionally contains code with the following licenses:\nMIT, ISC, BSD-2-Clause, BSD-3-Clause, Apache-2.0, 0BSD\n\n## Bundled dependencies\n\n### @angular/compiler@v12.2.16\n\nLicense: MIT\nBy: angular\nRepository: <https://github.com/angular/angular.git>\n\n----------------------------------------\n\n### @babel/code-frame@v7.18.6\n\nLicense: MIT\nBy: The Babel Team\nRepository: <https://github.com/babel/babel.git>\n\n> MIT License\n>\n> Copyright (c) 2014-present Sebastian McKenzie and other contributors\n>\n> Permission is hereby granted, free of charge, to any person obtaining\n> a copy of this software and associated documentation files (the\n> \"Software\"), to deal in the Software without restriction, including\n> without limitation the rights to use, copy, modify, merge, publish,\n> distribute, sublicense, and/or sell copies of the Software, and to\n> permit persons to whom the Software is furnished to do so, subject to\n> the following conditions:\n>\n> The above copyright notice and this permission notice shall be\n> included in all copies or substantial portions of the Software.\n>\n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n----------------------------------------\n\n### @babel/helper-validator-identifier@v7.19.1\n\nLicense: MIT\nBy: The Babel Team\nRepository: <https://github.com/babel/babel.git>\n\n> MIT License\n>\n> Copyright (c) 2014-present Sebastian McKenzie and other contributors\n>\n> Permission is hereby granted, free of charge, to any person obtaining\n> a copy of this software and associated documentation files (the\n> \"Software\"), to deal in the Software without restriction, including\n> without limitation the rights to use, copy, modify, merge, publish,\n> distribute, sublicense, and/or sell copies of the Software, and to\n> permit persons to whom the Software is furnished to do so, subject to\n> the following conditions:\n>\n> The above copyright notice and this permission notice shall be\n> included in all copies or substantial portions of the Software.\n>\n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n----------------------------------------\n\n### @babel/highlight@v7.18.6\n\nLicense: MIT\nBy: The Babel Team\nRepository: <https://github.com/babel/babel.git>\n\n> MIT License\n>\n> Copyright (c) 2014-present Sebastian McKenzie and other contributors\n>\n> Permission is hereby granted, free of charge, to any person obtaining\n> a copy of this software and associated documentation files (the\n> \"Software\"), to deal in the Software without restriction, including\n> without limitation the rights to use, copy, modify, merge, publish,\n> distribute, sublicense, and/or sell copies of the Software, and to\n> permit persons to whom the Software is furnished to do so, subject to\n> the following conditions:\n>\n> The above copyright notice and this permission notice shall be\n> included in all copies or subLine truncated
},
{
"author": "James Birtles <jameshbirtles@gmail.com>",
"repo": {
"type": "git",
"url": "https://github.com/sveltejs/prettier-plugin-svelte"
},
"description": "Svelte plugin for prettier",
"name": "prettier-plugin-svelte",
"license": "MIT",
"version": "2.10.0",
"licensetext": "MIT License\r\n\r\nCopyright (c) 2019 [Contributors](https://github.com/sveltejs/prettier-plugin-svelte/graphs/contributors)\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
},
{
"author": "Lars Kappert",
"repo": {
"type": "git",
"url": "https://github.com/release-it/release-it"
},
"description": "Generic CLI tool to automate versioning and package publishing-related tasks.",
"name": "release-it",
"license": "MIT",
"version": "15.10.3",
"licensetext": "MIT License\n\nCopyright (c) 2018 Lars Kappert\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"author": "rob-balfre",
"repo": { "url": "https://rob-balfre@github.com/rob-balfre/svelte-select" },
"description": "A <Select> component for Svelte apps",
"name": "svelte-select",
"license": "LIL",
"version": "3.17.0",
"licensetext": "Copyright (c) 2019 Robert Balfre\n\nPermission is hereby granted by the authors of this software, to any person, to use the software for any purpose, free of charge, including the rights to run, read, copy, change, distribute and sell it, and including usage rights to any patents the authors may hold on it, subject to the following conditions:\n\nThis license, or a link to its text, must be included with all copies of the software and any derivative works.\n\nAny modification to the software submitted to the authors may be incorporated into the software under the terms of this license.\n\nThe software is provided \"as is\", without warranty of any kind, including but not limited to the warranties of title, fitness, merchantability and non-infringement. The authors have no obligation to provide support or updates for the software, and may not be held liable for any damages, claims or other liability arising from its use.\n"
},
{
"author": "?",
"repo": { "url": "https://github.com/tailwindlabs/tailwindcss" },
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
"name": "tailwindcss",
"license": "MIT",
"version": "3.3.2",
"licensetext": "MIT License\n\nCopyright (c) Tailwind Labs, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"author": "Evan You",
"repo": {
"type": "git",
"url": "https://github.com/vitejs/vite",
"directory": "packages/vite"
},
"description": "Native-ESM powered web dev build tool",
"name": "vite",
"license": "MIT",
"version": "4.3.3",
"licensetext": "# Vite core license\nVite is released under the MIT license:\n\nMIT License\n\nCopyright (c) 2019-present, Yuxi (Evan) You and Vite contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n# Licenses of bundled dependencies\nThe published Vite artifact additionally contains code with the following licenses:\nApache-2.0, BSD-2-Clause, CC0-1.0, ISC, MIT\n\n# Bundled dependencies:\n## @ampproject/remapping\nLicense: Apache-2.0\nBy: Justin Ridgewell\nRepository: git+https://github.com/ampproject/remapping.git\n\n> Apache License\n> Version 2.0, January 2004\n> http://www.apache.org/licenses/\n> \n> TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n> \n> 1. Definitions.\n> \n> \"License\" shall mean the terms and conditions for use, reproduction,\n> and distribution as defined by Sections 1 through 9 of this document.\n> \n> \"Licensor\" shall mean the copyright owner or entity authorized by\n> the copyright owner that is granting the License.\n> \n> \"Legal Entity\" shall mean the union of the acting entity and all\n> other entities that control, are controlled by, or are under common\n> control with that entity. For the purposes of this definition,\n> \"control\" means (i) the power, direct or indirect, to cause the\n> direction or management of such entity, whether by contract or\n> otherwise, or (ii) ownership of fifty percent (50%) or more of the\n> outstanding shares, or (iii) beneficial ownership of such entity.\n> \n> \"You\" (or \"Your\") shall mean an individual or Legal Entity\n> exercising permissions granted by this License.\n> \n> \"Source\" form shall mean the preferred form for making modifications,\n> including but not limited to software source code, documentation\n> source, and configuration files.\n> \n> \"Object\" form shall mean any form resulting from mechanical\n> transformation or translation of a Source form, including but\n> not limited to compiled object code, generated documentation,\n> and conversions to other media types.\n> \n> \"Work\" shall mean the work of authorship, whether in Source or\n> Object form, made available under the License, as indicated by a\n> copyright notice that is included in or attached to the work\n> (an example is provided in the Appendix below).\n> \n> \"Derivative Works\" shall mean any work, whether in Source or Object\n> form, that is based on (or derived from) the Work and for which the\n> editorial revisions, annotations, elaborations, or other modifications\n> represent, as a whole, an original work of authorship. For the purposes\n> of this License, Derivative Works shall not include works that remain\n> separable from, or merely link (or bind by name) to the interfaces of,\n> the Work and Derivative Works thereof.\n> \n> \"Contribution\" shall mean any work of authorship, including\n> the original version of the Work and any modifications or additions\n> to that Work or Derivative Works thereof, that is intentionally\n> submitted to Licensor for inclusion in the Work by the copyright owner\n> or by an individual or Legal Entity authorized to submit on behalf of\n> the copyright owner. For the purposes of this definition, \"submitted\"\n> means any form of electronic, verbal, or written communication sent\n> to the Licensor or its representatives, including but not limited to\n> communication on electronic mailing lists, source code control systems,\n> and issue tracking systems that are managed by, or on behalf of, the\n> Licensor for the purpose of discussing and improving the Work, but\n> excluding communication that is conspicuously marked or otherwise\n> designated in writing by the copyright owner as \"Not a CLine truncated
}
]
[{"author":"deanilvincent","repo":{"type":"git","url":"https://github.com/deanilvincent/check-password-strength"},"description":"A NPM Password strength checker based from Javascript RegExp. Check passphrase if it's \"Weak\", \"Medium\" or \"Strong\"","name":"check-password-strength","license":"MIT","version":"2.0.2","licensetext":"MIT License\r\n\r\nCopyright (c) 2020 Mark Deanil Vicente\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"},{"author":"ODIT.Services","repo":{"type":"git","url":"git+https://git.odit.services/lfk/lfk-client-js"},"description":"A lib to interact with https://git.odit.services/lfk/backend. Use this version for native JS applications.","name":"@odit/lfk-client-js","license":"CC-BY-NC-SA-4.0","version":"0.10.1","licensetext":"Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Creative\r\nCommons Corporation (\"Creative Commons\") is not a law firm and does not provide\r\nlegal services or legal advice. Distribution of Creative Commons public licenses\r\ndoes not create a lawyer-client or other relationship. Creative Commons makes\r\nits licenses and related information available on an \"as-is\" basis. Creative\r\nCommons gives no warranties regarding its licenses, any material licensed\r\nunder their terms and conditions, or any related information. Creative Commons\r\ndisclaims all liability for damages resulting from their use to the fullest\r\nextent possible.\r\n\r\nUsing Creative Commons Public Licenses\r\n\r\nCreative Commons public licenses provide a standard set of terms and conditions\r\nthat creators and other rights holders may use to share original works of\r\nauthorship and other material subject to copyright and certain other rights\r\nspecified in the public license below. The following considerations are for\r\ninformational purposes only, are not exhaustive, and do not form part of our\r\nlicenses.\r\n\r\nConsiderations for licensors: Our public licenses are intended for use by\r\nthose authorized to give the public permission to use material in ways otherwise\r\nrestricted by copyright and certain other rights. Our licenses are irrevocable.\r\nLicensors should read and understand the terms and conditions of the license\r\nthey choose before applying it. Licensors should also secure all rights necessary\r\nbefore applying our licenses so that the public can reuse the material as\r\nexpected. Licensors should clearly mark any material not subject to the license.\r\nThis includes other CC-licensed material, or material used under an exception\r\nor limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors\r\n\r\nConsiderations for the public: By using one of our public licenses, a licensor\r\ngrants the public permission to use the licensed material under specified\r\nterms and conditions. If the licensor's permission is not necessary for any\r\nreason–for example, because of any applicable exception or limitation to copyright–then\r\nthat use is not regulated by the license. Our licenses grant only permissions\r\nunder copyright and certain other rights that a licensor has authority to\r\ngrant. Use of the licensed material may still be restricted for other reasons,\r\nincluding because others have copyright or other rights in the material. A\r\nlicensor may make special requests, such as asking that all changes be marked\r\nor described. Although not required by our licenses, you are encouraged to\r\nrespect those requests where reasonable. More considerations for the public\r\n: wiki.creativecommons.org/Considerations_for_licensees\r\n\r\nCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public\r\nLicense\r\n\r\nBy exercising the Licensed Rights (defined below), You accept and agree to\r\nbe bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike\r\n4.0 International Public License (\"Public License\"). ToLine truncated
+4
View File
@@ -0,0 +1,4 @@
<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>

After

Width:  |  Height:  |  Size: 1.1 KiB

+32 -37
View File
@@ -1,39 +1,34 @@
{
"name": "Lauf für Kaya! - Admin",
"short_name": "LfK!Admin",
"start_url": "/?utm_source=pwa",
"orientation": "portrait-primary",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#fff",
"description": "Lauf für Kaya! - Admin",
"shortcuts": [
{
"name": "Users",
"url": "/users/?utm_source=pwa"
}
],
"icons": [
{
"src": "/favicon.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "/favicon.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/lfk-logo.png",
"sizes": "1540x144",
"type": "image/png"
},
{
"src": "/maskable_icon_x1.png",
"sizes": "750x750",
"type": "image/png",
"purpose": "any maskable"
}
]
"name": "Lauf für Kaya! - Admin",
"short_name": "LfK!Admin",
"start_url": "/?utm_source=pwa",
"orientation": "portrait-primary",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#fff",
"description": "Lauf für Kaya! - Admin",
"shortcuts": [
{
"name": "Users",
"url": "/users/?utm_source=pwa"
}
],
"icons": [
{
"src": "/favicon.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "/favicon.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/lfk-logo.png",
"sizes": "1540x144",
"type": "image/png"
},
{ "src": "/maskable_icon_x1.png", "sizes": "750x750", "type": "image/png", "purpose": "any maskable" }
]
}
+1 -1
View File
@@ -1 +1 @@
Nostrud tempor dolor aute ea excepteur aute mollit elit eiusmod exercitation. Magna laborum pariatur adipisicing pariatur cupidatat exercitation duis aliquip pariatur sint exercitation deserunt labore. Consectetur id laboris dolore nostrud do velit ipsum. Eu laboris velit do commodo ad ea sint ex cillum. Cillum ipsum qui eiusmod laborum mollit sunt dolore incididunt. Cillum sunt culpa veniam voluptate et qui ut magna anim occaecat ut mollit dolor. Duis irure proident eu incididunt dolore sunt nisi aute dolore amet eu fugiat laboris quis.
Nostrud tempor dolor aute ea excepteur aute mollit elit eiusmod exercitation. Magna laborum pariatur adipisicing pariatur cupidatat exercitation duis aliquip pariatur sint exercitation deserunt labore. Consectetur id laboris dolore nostrud do velit ipsum. Eu laboris velit do commodo ad ea sint ex cillum. Cillum ipsum qui eiusmod laborum mollit sunt dolore incididunt. Cillum sunt culpa veniam voluptate et qui ut magna anim occaecat ut mollit dolor. Duis irure proident eu incididunt dolore sunt nisi aute dolore amet eu fugiat laboris quis.
+9 -22
View File
@@ -1,4 +1,6 @@
<script>
import "toastify-js/src/toastify.css";
import "gridjs/dist/theme/mermaid.css";
import { Route, router } from "tinro";
router.subscribe((routeInfo) => {
window.scrollTo(0, 0);
@@ -22,10 +24,11 @@
name: "lfk_admin",
version: 1.0,
storeName: "lfk_admin",
description: "LfK! admin dashboard",
description: "LfK! admin dashbaord",
});
window.onunhandledrejection = (event) => {
if (event.reason.toString() == "Error: Unauthorized") {
console.log("Found 1");
localForage.clear();
location.replace("/");
}
@@ -41,7 +44,6 @@
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";
@@ -70,29 +72,27 @@
import Scans from "./components/scans/Scans.svelte";
import ScanDetail from "./components/scans/ScanDetail.svelte";
import Cards from "./components/cards/Cards.svelte";
import StatsClients from "./components/statsclients/StatsClients.svelte";
import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte";
store.init();
</script>
<Route>
{#if $router.path === "/forgot_password"}
{#if $router.path === '/forgot_password'}
<Route path="/forgot_password">
<ForgotPassword />
</Route>
{:else if $router.path.includes("/reset")}
{:else if $router.path.includes('/reset')}
<Route path="/reset/:resetkey" let:params>
<ResetPassword {params} />
</Route>
{:else if $router.path === "/about"}
{:else if $router.path === '/about'}
<Route path="/about">
<About />
</Route>
{:else if $router.path === "/imprint"}
{:else if $router.path === '/imprint'}
<Route path="/imprint">
<Imprint />
</Route>
{:else if $router.path === "/privacy"}
{:else if $router.path === '/privacy'}
<Route path="/privacy">
<Privacy />
</Route>
@@ -142,11 +142,6 @@
<RunnerDetail {params} />
</Route>
</Route>
<Route path="/cardassignment/*">
<Route path="/">
<CardAssignment />
</Route>
</Route>
<Route path="/teams/*">
<Route path="/">
<Teams />
@@ -211,14 +206,6 @@
<ScanStationDetail {params} />
</Route>
</Route>
<Route path="/statsclients/*">
<Route path="/">
<StatsClients />
</Route>
<Route path="/:clientid" let:params>
<StatsClientDetail {params} />
</Route>
</Route>
<Route path="/about">
<About />
</Route>
+31 -36
View File
@@ -1,22 +1,28 @@
<script>
import { AuthService } from "@odit/lfk-client-js";
import toast from "svelte-french-toast";
import { ApiError, AuthService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import "toastify-js/src/toastify.css";
import isEmail from "validator/es/lib/isEmail";
let reset_mail_sent = false;
let usersEmail = "";
function reset() {
if (isEmail(usersEmail)) {
toast.loading($_("mail-validation-in-progress"));
AuthService.authControllerGetResetToken("de", { email: usersEmail })
.then((resp) => {
toast.dismiss();
Toastify({
text: $_("mail-validation-in-progress"),
duration: 3500,
}).showToast();
reset_mail_sent = true;
})
.catch((err) => {});
} else {
toast($_("invalid-mail-reset"));
Toastify({
text: $_("invalid-mail-reset"),
duration: 3500,
}).showToast();
}
}
</script>
@@ -26,18 +32,17 @@
<div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_("application_name")}
{$_('application_name')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_("password-reset-mail-sent", { values: { usersEmail: usersEmail } })}
{$_('password-reset-mail-sent', { values: { usersEmail: usersEmail } })}
</p>
<div class="mt-6">
<div class="mt-6">
<a
href="/"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
>
{$_("goback")}
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm">
{$_('goback')}
</a>
</div>
</div>
@@ -48,26 +53,25 @@
<div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_("application_name")}
{$_('application_name')}
</p>
<p class="mt-6 text-sm text-center text-gray-900">
{$_("forgot_password")}
{$_('forgot_password')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_("dont-panic-were-resetting-it")}
{$_('dont-panic-were-resetting-it')}
</p>
<div>
<div class="rounded-md shadow-sm">
<div>
<input
aria-label={$_("e-mail-adress")}
aria-label={$_('e-mail-adress')}
name="email"
type="email"
required=""
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border text-gray-900 rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
placeholder={$_("e-mail-adress")}
bind:value={usersEmail}
/>
placeholder={$_('e-mail-adress')}
bind:value={usersEmail} />
</div>
</div>
@@ -75,22 +79,19 @@
<button
on:click={reset}
type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"
>
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
<span class="absolute left-0 inset-y pl-3">
<svg
class="h-5 w-5 text-gray-500"
fill="currentColor"
viewBox="0 0 20 20"
>
viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
/>
clip-rule="evenodd" />
</svg>
</span>
{$_("reset-my-password")}
{$_('reset-my-password')}
</button>
</div>
<div class="mt-6">
@@ -99,30 +100,24 @@
<div class="w-full border-t border-gray-300" />
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-gray-100 text-gray-500"
>{$_("dont-have-your-email-connected")}</span
>
<span
class="px-2 bg-gray-100 text-gray-500">{$_('dont-have-your-email-connected')}</span>
</div>
</div>
<span
class="mt-2 text-sm px-2 bg-gray-100 text-gray-500 justify-center relative flex"
>{$_("cannot-reset-your-password-directly")}</span
>
class="mt-2 text-sm px-2 bg-gray-100 text-gray-500 justify-center relative flex">{$_('cannot-reset-your-password-directly')}</span>
<div class="mt-6">
<a
href="mailto:lfk@odit.services"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
>
{$_("send-a-mail-to-lfk-odit-services")}
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm">
{$_('send-a-mail-to-lfk-odit-services')}
</a>
</div>
<div class="mt-6">
<a
href="/"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
>{$_("goback")}</a
>
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm">{$_('goback')}</a>
</div>
</div>
</div>
+43 -29
View File
@@ -6,7 +6,7 @@
import { OpenAPI, AuthService } from "@odit/lfk-client-js";
import Footer from "../general/Footer.svelte";
import isEmail from "validator/es/lib/isEmail";
import toast from "svelte-french-toast";
import Toastify from "toastify-js";
// ------
let username = config.default_username || "";
let password = config.default_password || "";
@@ -20,6 +20,11 @@
OpenAPI.TOKEN = value.access_token;
const jwtinfo = JSON.parse(atob(OpenAPI.TOKEN.split(".")[1]));
store.login(value, jwtinfo);
Toastify({
text: $_("welcome_wavinghand"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
}
});
@@ -28,7 +33,10 @@
// prevent login button spamming
if (last_loginclick_processed && is_blocked_by_autologin === false) {
last_loginclick_processed = false;
toast.loading($_("login_is_checked"));
Toastify({
text: $_("login_is_checked"),
duration: 500,
}).showToast();
let postdata = {};
if (isEmail(username)) {
postdata = {
@@ -48,18 +56,31 @@
const jwtinfo = JSON.parse(atob(OpenAPI.TOKEN.split(".")[1]));
store.login(result.access_token, jwtinfo);
location.replace("/");
toast.dismiss();
Toastify({
text: $_("welcome_wavinghand"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
toast.dismiss();
toast.error($_("error_on_login"));
Toastify({
text: $_("error_on_login"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
})
.finally(() => {
last_loginclick_processed = true;
});
// last login was not processed yet
} else {
toast($_("please-wait-a-moment-your-login-is-still-being-processed"));
Toastify({
text: "chill...",
duration: 1500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
}
};
function handleKeydown(e) {
@@ -70,37 +91,34 @@
</script>
<div
class="min-h-screen flex items-center justify-center bg-gray-100 text-gray-900"
>
class="min-h-screen flex items-center justify-center bg-gray-100 text-gray-900">
<div class="max-w-md w-full py-12 px-6" role="main">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-xl text-center font-bold">{$_("application_name")}</p>
<p class="mt-2 mb-6 text-sm text-center">{$_("log_in_to_your_account")}</p>
<p class="mt-6 text-lg text-center font-bold">{$_('application_name')}</p>
<p class="mt-6 text-sm text-center">{$_('log_in_to_your_account')}</p>
<div>
<div class="rounded-md shadow-sm">
<div>
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
aria-label={$_("email_address_or_username")}
aria-label={$_('email_address_or_username')}
type="text"
required=""
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
on:keydown={handleKeydown}
placeholder={$_("email_address_or_username")}
bind:value={username}
/>
placeholder={$_('email_address_or_username')}
bind:value={username} />
</div>
<div class="-mt-px relative">
<input
aria-label={$_("password")}
aria-label={$_('password')}
type="password"
required=""
bind:value={password}
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-b-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
on:keydown={handleKeydown}
placeholder={$_("password")}
/>
placeholder={$_('password')} />
</div>
</div>
@@ -108,33 +126,29 @@
<button
on:click={login}
type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"
>
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
<span class="absolute left-0 inset-y pl-3">
<svg
class="h-5 w-5 text-gray-500"
fill="currentColor"
viewBox="0 0 20 20"
>
viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
/>
clip-rule="evenodd" />
</svg>
</span>
{$_("log_in")}
{$_('log_in')}
</button>
</div>
</div>
<!-- <div class="mt-2">
<div class="mt-2">
<a
href="/forgot_password"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
>
{$_("forgot_password")}
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm">
{$_('forgot_password')}
</a>
</div> -->
</div>
</div>
</div>
<Footer />
+43 -43
View File
@@ -1,52 +1,52 @@
<script context="module">
import { passwordStrength } from "check-password-strength";
export function password_strong_enough(password_change) {
let strength = passwordStrength(password_change);
return (
strength?.contains.includes("lowercase") &&
strength?.contains.includes("uppercase") &&
strength?.contains.includes("number") &&
strength?.length > 9
);
}
export function password_strong_enough_and_equal(
password_change,
password_confirm
) {
return (
password_strong_enough(password_change) &&
password_change === password_confirm
);
}
import { passwordStrength } from "check-password-strength";
export function password_strong_enough(password_change) {
let strength = passwordStrength(password_change);
return (
strength?.contains.includes("lowercase") &&
strength?.contains.includes("uppercase") &&
strength?.contains.includes("number") &&
strength?.length > 9
);
}
export function password_strong_enough_and_equal(
password_change,
password_confirm
) {
return (
password_strong_enough(password_change) &&
password_change === password_confirm
);
}
</script>
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { passwordStrength as Strength } from "check-password-strength";
export let password_change;
export let password_confirm;
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { passwordStrength as Strength } from "check-password-strength";
export let password_change;
export let password_confirm;
$: strength = Strength(password_change);
$: passwords_match =
!password_confirm || password_confirm === password_change;
$: strength = Strength(password_change);
$: passwords_match =
!password_confirm || password_confirm === password_change;
</script>
<div class="ml-4">
<ul class="list-disc font-medium tracking-wide text-red-500 text-xs">
{#if !strength.contains.includes("lowercase")}
<li>{$_("must-contain-a-lowercase-letter")}</li>
{/if}
{#if !strength.contains.includes("uppercase")}
<li>{$_("must-contain-a-uppercase-letter")}</li>
{/if}
{#if !strength.contains.includes("number")}
<li>{$_("must-contain-a-number")}</li>
{/if}
{#if !(strength.length > 9)}
<li>{$_("must-be-at-least-10-characters-long")}</li>
{/if}
{#if !(passwords_match == true)}
<li>{$_("passwords-dont-match")}</li>
{/if}
</ul>
<ul class="list-disc font-medium tracking-wide text-red-500 text-xs">
{#if !strength.contains.includes('lowercase')}
<li>{$_('must-contain-a-lowercase-letter')}</li>
{/if}
{#if !strength.contains.includes('uppercase')}
<li>{$_('must-contain-a-uppercase-letter')}</li>
{/if}
{#if !strength.contains.includes('number')}
<li>{$_('must-contain-a-number')}</li>
{/if}
{#if !(strength.length > 9)}
<li>{$_('must-be-at-least-10-characters-long')}</li>
{/if}
{#if !(passwords_match == true)}
<li>{$_('passwords-dont-match')}</li>
{/if}
</ul>
</div>
+36 -34
View File
@@ -1,7 +1,8 @@
<script>
import { AuthService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import toast from "svelte-french-toast";
import Toastify from "toastify-js";
import "toastify-js/src/toastify.css";
import PasswordStrength, {
password_strong_enough,
} from "../auth/PasswordStrength.svelte";
@@ -10,97 +11,101 @@
export let params;
function set_new_password() {
if (password.trim() !== "") {
toast.loading($_("password-reset-in-progress"));
Toastify({
text: $_("password-reset-in-progress"),
duration: 3500,
}).showToast();
AuthService.authControllerResetPassword(atob(params.resetkey), {
password,
})
.then((resp) => {
toast.dismiss();
toast($_("password-reset-successful"));
Toastify({
text: $_("password-reset-successful"),
duration: 3500,
}).showToast();
state = "reset_success";
})
.catch((err) => {
state = "reset_error";
});
} else {
toast.dismiss();
toast.error($_("please-provide-a-password"));
Toastify({
text: $_("please-provide-a-password"),
duration: 3500,
}).showToast();
}
}
</script>
{#if state === "reset_success"}
{#if state === 'reset_success'}
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_("application_name")}
{$_('application_name')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold">
{$_("successful-password-reset")}
{$_('successful-password-reset')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_("you-can-now-use-your-new-password-to-log-in-to-your-account")}
{$_('you-can-now-use-your-new-password-to-log-in-to-your-account')}
</p>
<div class="mt-6">
<div class="mt-6">
<a
href="/login/"
class="text-center relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"
>
{$_("go-to-login")}
class="text-center relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
{$_('go-to-login')}
</a>
</div>
</div>
</div>
</div>
{:else if state === "reset_error"}
{:else if state === 'reset_error'}
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_("application_name")}
{$_('application_name')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold">
{$_("password-reset-failed")}
{$_('password-reset-failed')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_("please-request-a-new-reset-mail")}
{$_('please-request-a-new-reset-mail')}
</p>
<div class="mt-6">
<div class="mt-6">
<a
href="/forgot_password/"
class="text-center relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"
>
{$_("request-a-new-reset-mail")}
class="text-center relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
{$_('request-a-new-reset-mail')}
</a>
</div>
</div>
</div>
</div>
{:else if state === "reset_in_progress"}
{:else if state === 'reset_in_progress'}
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_("application_name")}
{$_('application_name')}
</p>
<p class="mt-2 mb-4 text-md text-center text-gray-900">
{$_("reset-password")}
{$_('reset-password')}
</p>
<div>
<div class="rounded-md shadow-sm">
<div>
<input
aria-label={$_("new-password")}
aria-label={$_('new-password')}
name="password"
type="password"
required=""
class="border-gray-300 placeholder-gray-500 appearance-none rounded-md relative block w-full px-3 py-2 border text-gray-900 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
placeholder={$_("new-password")}
bind:value={password}
/>
placeholder={$_('new-password')}
bind:value={password} />
</div>
<PasswordStrength bind:password_change={password} />
</div>
@@ -111,22 +116,19 @@
disabled={!password_strong_enough(password)}
class:opacity-50={!password_strong_enough(password)}
type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"
>
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
<span class="absolute left-0 inset-y pl-3">
<svg
class="h-5 w-5 text-gray-500"
fill="currentColor"
viewBox="0 0 20 20"
>
viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
/>
clip-rule="evenodd" />
</svg>
</span>
{$_("reset-my-password")}
{$_('reset-my-password')}
</button>
</div>
</div>
+274
View File
@@ -0,0 +1,274 @@
<!--
This example requires Tailwind CSS v2.0+
This example requires some changes to your config:
```
// tailwind.config.js
module.exports = {
// ...
plugins: [
// ...
require('@tailwindcss/forms'),
]
}
```
-->
<div>
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">Profile</h3>
<p class="mt-1 text-sm text-gray-600">
This information will be displayed publicly so be careful what you share.
</p>
</div>
</div>
<div class="mt-5 md:mt-0 md:col-span-2">
<form action="#" method="POST">
<div class="shadow sm:rounded-md sm:overflow-hidden">
<div class="px-4 py-5 bg-white space-y-6 sm:p-6">
<div class="grid grid-cols-3 gap-6">
<div class="col-span-3 sm:col-span-2">
<label for="company_website" class="block text-sm font-medium text-gray-700">
Website
</label>
<div class="mt-1 flex rounded-md shadow-sm">
<span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 text-sm">
http://
</span>
<input type="text" name="company_website" id="company_website" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-r-md sm:text-sm border-gray-300" placeholder="www.example.com">
</div>
</div>
</div>
<div>
<label for="about" class="block text-sm font-medium text-gray-700">
About
</label>
<div class="mt-1">
<textarea id="about" name="about" rows="3" class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="you@example.com"></textarea>
</div>
<p class="mt-2 text-sm text-gray-500">
Brief description for your profile. URLs are hyperlinked.
</p>
</div>
<div>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block text-sm font-medium text-gray-700">
Photo
</label>
<div class="mt-2 flex items-center">
<span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">
<svg class="h-full w-full text-gray-300" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</span>
<button type="button" class="ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Change
</button>
</div>
</div>
<div>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block text-sm font-medium text-gray-700">
Cover photo
</label>
<div class="mt-2 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
<div class="space-y-1 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div class="flex text-sm text-gray-600">
<label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
<span>Upload a file</span>
<input id="file-upload" name="file-upload" type="file" class="sr-only">
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs text-gray-500">
PNG, JPG, GIF up to 10MB
</p>
</div>
</div>
</div>
</div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Save
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="hidden sm:block" aria-hidden="true">
<div class="py-5">
<div class="border-t border-gray-200"></div>
</div>
</div>
<div class="mt-10 sm:mt-0">
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">Personal Information</h3>
<p class="mt-1 text-sm text-gray-600">
Use a permanent address where you can receive mail.
</p>
</div>
</div>
<div class="mt-5 md:mt-0 md:col-span-2">
<form action="#" method="POST">
<div class="shadow overflow-hidden sm:rounded-md">
<div class="px-4 py-5 bg-white sm:p-6">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<label for="first_name" class="block text-sm font-medium text-gray-700">First name</label>
<input type="text" name="first_name" id="first_name" autocomplete="given-name" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-3">
<label for="last_name" class="block text-sm font-medium text-gray-700">Last name</label>
<input type="text" name="last_name" id="last_name" autocomplete="family-name" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-4">
<label for="email_address" class="block text-sm font-medium text-gray-700">Email address</label>
<input type="text" name="email_address" id="email_address" autocomplete="email" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-3">
<label for="country" class="block text-sm font-medium text-gray-700">Country / Region</label>
<select id="country" name="country" autocomplete="country" class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option>United States</option>
<option>Canada</option>
<option>Mexico</option>
</select>
</div>
<div class="col-span-6">
<label for="street_address" class="block text-sm font-medium text-gray-700">Street address</label>
<input type="text" name="street_address" id="street_address" autocomplete="street-address" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-6 lg:col-span-2">
<label for="city" class="block text-sm font-medium text-gray-700">City</label>
<input type="text" name="city" id="city" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-3 lg:col-span-2">
<label for="state" class="block text-sm font-medium text-gray-700">State / Province</label>
<input type="text" name="state" id="state" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
<div class="col-span-6 sm:col-span-3 lg:col-span-2">
<label for="postal_code" class="block text-sm font-medium text-gray-700">ZIP / Postal</label>
<input type="text" name="postal_code" id="postal_code" autocomplete="postal-code" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
</div>
</div>
</div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Save
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="hidden sm:block" aria-hidden="true">
<div class="py-5">
<div class="border-t border-gray-200"></div>
</div>
</div>
<div class="mt-10 sm:mt-0">
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">Notifications</h3>
<p class="mt-1 text-sm text-gray-600">
Decide which communications you'd like to receive and how.
</p>
</div>
</div>
<div class="mt-5 md:mt-0 md:col-span-2">
<form action="#" method="POST">
<div class="shadow overflow-hidden sm:rounded-md">
<div class="px-4 py-5 bg-white space-y-6 sm:p-6">
<fieldset>
<legend class="text-base font-medium text-gray-900">By Email</legend>
<div class="mt-4 space-y-4">
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="comments" name="comments" type="checkbox" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded">
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-medium text-gray-700">Comments</label>
<p class="text-gray-500">Get notified when someones posts a comment on a posting.</p>
</div>
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="candidates" name="candidates" type="checkbox" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded">
</div>
<div class="ml-3 text-sm">
<label for="candidates" class="font-medium text-gray-700">Candidates</label>
<p class="text-gray-500">Get notified when a candidate applies for a job.</p>
</div>
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="offers" name="offers" type="checkbox" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded">
</div>
<div class="ml-3 text-sm">
<label for="offers" class="font-medium text-gray-700">Offers</label>
<p class="text-gray-500">Get notified when a candidate accepts or rejects an offer.</p>
</div>
</div>
</div>
</fieldset>
<fieldset>
<div>
<legend class="text-base font-medium text-gray-900">Push Notifications</legend>
<p class="text-sm text-gray-500">These are delivered via SMS to your mobile phone.</p>
</div>
<div class="mt-4 space-y-4">
<div class="flex items-center">
<input id="push_everything" name="push_notifications" type="radio" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300">
<label for="push_everything" class="ml-3 block text-sm font-medium text-gray-700">
Everything
</label>
</div>
<div class="flex items-center">
<input id="push_email" name="push_notifications" type="radio" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300">
<label for="push_email" class="ml-3 block text-sm font-medium text-gray-700">
Same as email
</label>
</div>
<div class="flex items-center">
<input id="push_nothing" name="push_notifications" type="radio" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300">
<label for="push_nothing" class="ml-3 block text-sm font-medium text-gray-700">
No push notifications
</label>
</div>
</div>
</fieldset>
</div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Save
</button>
</div>
</div>
</form>
</div>
</div>
</div>
+5 -8
View File
@@ -4,22 +4,19 @@
<body class="antialiased font-sans">
<div class="flex min-h-screen">
<div class="w-full bg-white flex items-center justify-center">
<div class="w-full bg-white flex items-center justify-center ">
<div class="max-w-sm m-8">
<div class="text-black text-5xl md:text-15xl font-black">
{$_("internal-error")}
{$_('internal-error')}
</div>
<div class="w-16 h-1 bg-purple-light my-3 md:my-6" />
<p
class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal"
>
{$_("generic-ui-logic-error")}
class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal">
{$_('generic-ui-logic-error')}
</p>
<a
href="/"
class="bg-transparent text-grey-darkest font-bold uppercase tracking-wide py-3 px-6 border-2 border-grey-light hover:border-grey rounded-lg"
>{$_("goback")}</a
>
class="bg-transparent text-grey-darkest font-bold uppercase tracking-wide py-3 px-6 border-2 border-grey-light hover:border-grey rounded-lg">{$_('goback')}</a>
</div>
</div>
</div>
+1 -1
View File
@@ -5,7 +5,7 @@
<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>
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
+22 -23
View File
@@ -1,25 +1,24 @@
export function getlang(langkeys) {
return {
search: {
placeholder: langkeys.search,
},
sort: {
sortAsc: langkeys.sort_column_ascending,
sortDesc: langkeys.sort_column_descending,
},
pagination: {
previous: langkeys.previous,
next: langkeys.next,
navigate: (page, pages) =>
`${langkeys.page} ${page} ${langkeys.of} ${pages}`,
page: (page) => `${langkeys.page} ${page}`,
showing: langkeys.showing,
of: langkeys.of,
to: langkeys.to,
results: langkeys.records,
},
loading: langkeys.loading,
noRecordsFound: langkeys.no_matching_records_found,
error: langkeys.an_error_happened_while_fetching_the_data,
};
return {
search: {
placeholder: langkeys.search
},
sort: {
sortAsc: langkeys.sort_column_ascending,
sortDesc: langkeys.sort_column_descending
},
pagination: {
previous: langkeys.previous,
next: langkeys.next,
navigate: (page, pages) => `${langkeys.page} ${page} ${langkeys.of} ${pages}`,
page: (page) => `${langkeys.page} ${page}`,
showing: langkeys.showing,
of: langkeys.of,
to: langkeys.to,
results: langkeys.records
},
loading: langkeys.loading,
noRecordsFound: langkeys.no_matching_records_found,
error: langkeys.an_error_happened_while_fetching_the_data
};
}
-6
View File
@@ -1,6 +0,0 @@
<!--
Temporary tailwind import fixes for classes that wouldn't be directly used otherwise.
Or as others may call it: Real big bullshit time.
Issue: https://git.odit.services/lfk/frontend/issues/136
-->
<div class="opacity-50" />
+7 -7
View File
@@ -1,10 +1,10 @@
/** Dispatch event on click outside of node */
export function clickOutside(node) {
const handleClick = (event) => {
if (event.target.getAttribute("data-id") === "modal_backdrop") {
node.dispatchEvent(new CustomEvent("click_outside", node));
}
};
document.removeEventListener("click", handleClick, true);
document.addEventListener("click", handleClick, true);
const handleClick = (event) => {
if (event.target.getAttribute('data-id') === 'modal_backdrop') {
node.dispatchEvent(new CustomEvent('click_outside', node));
}
};
document.removeEventListener('click', handleClick, true);
document.addEventListener('click', handleClick, true);
}
+1 -323
View File
@@ -1,323 +1 @@
.simplecontent * {
margin: 0;
padding: 0;
}
.simplecontent address,
.simplecontent area,
.simplecontent article,
.simplecontent aside,
.simplecontent audio,
.simplecontent blockquote,
.simplecontent datalist,
.simplecontent details,
.simplecontent dl,
.simplecontent fieldset,
.simplecontent figure,
.simplecontent form,
.simplecontent iframe,
.simplecontent img,
.simplecontent input,
.simplecontent meter,
.simplecontent nav,
.simplecontent ol,
.simplecontent optgroup,
.simplecontent option,
.simplecontent output,
.simplecontent p,
.simplecontent pre,
.simplecontent progress,
.simplecontent ruby,
.simplecontent section,
.simplecontent table,
.simplecontent textarea,
.simplecontent ul,
.simplecontent video {
margin-bottom: 1rem;
}
.simplecontent button,
.simplecontent html,
.simplecontent input,
.simplecontent select {
font-family: var(--nc-font-sans);
}
.simplecontent body {
margin: 0 auto;
max-width: 750px;
padding: 2rem;
border-radius: 6px;
overflow-x: hidden;
word-break: break-word;
overflow-wrap: break-word;
background: var(--nc-bg-1);
color: var(--nc-tx-2);
font-size: 1.03rem;
line-height: 1.5;
}
.simplecontent h1,
.simplecontent h2,
.simplecontent h3,
.simplecontent h4,
.simplecontent h5,
.simplecontent h6 {
line-height: 1;
color: var(--nc-tx-1);
padding-top: 0.875rem;
}
.simplecontent h1,
.simplecontent h2,
.simplecontent h3 {
color: var(--nc-tx-1);
padding-bottom: 2px;
margin-bottom: 8px;
border-bottom: 1px solid var(--nc-bg-2);
}
.simplecontent h4,
.simplecontent h5,
.simplecontent h6 {
margin-bottom: 0.3rem;
}
.simplecontent h1 {
font-size: 2.25rem;
}
.simplecontent h2 {
font-size: 1.85rem;
}
.simplecontent h3 {
font-size: 1.55rem;
}
.simplecontent h4 {
font-size: 1.25rem;
}
.simplecontent h5 {
font-size: 1rem;
}
.simplecontent h6 {
font-size: 0.875rem;
}
.simplecontent a {
color: #3d5af1;
}
.simplecontent a:hover {
color: var(--nc-lk-2);
}
.simplecontent abbr:hover {
cursor: help;
}
.simplecontent blockquote {
padding: 1.5rem;
background: #ddd;
border-left: 5px solid var(--nc-bg-3);
}
.simplecontent abbr {
cursor: help;
}
.simplecontent blockquote :last-child {
padding-bottom: 0;
margin-bottom: 0;
}
.simplecontent header {
background: #ddd;
border-bottom: 1px solid var(--nc-bg-3);
padding: 2rem 1.5rem;
margin: -2rem calc(0px - (50vw - 50%)) 2rem;
padding-left: calc(50vw - 50%);
padding-right: calc(50vw - 50%);
}
.simplecontent header h1,
.simplecontent header h2,
.simplecontent header h3 {
padding-bottom: 0;
border-bottom: 0;
}
.simplecontent header > :first-child {
margin-top: 0;
padding-top: 0;
}
.simplecontent header > :last-child {
margin-bottom: 0;
}
.simplecontent a button,
.simplecontent button,
.simplecontent input[type="button"],
.simplecontent input[type="reset"],
.simplecontent input[type="submit"] {
font-size: 1rem;
display: inline-block;
padding: 6px 12px;
text-align: center;
text-decoration: none;
white-space: nowrap;
background: #3d5af1;
color: var(--nc-lk-tx);
border: 0;
border-radius: 4px;
box-sizing: border-box;
cursor: pointer;
color: var(--nc-lk-tx);
}
.simplecontent a button[disabled],
.simplecontent button[disabled],
.simplecontent input[type="button"][disabled],
.simplecontent input[type="reset"][disabled],
.simplecontent input[type="submit"][disabled] {
cursor: default;
opacity: 0.5;
cursor: not-allowed;
}
.simplecontent .button:focus,
.simplecontent .button:hover,
.simplecontent button:focus,
.simplecontent button:hover,
.simplecontent input[type="button"]:focus,
.simplecontent input[type="button"]:hover,
.simplecontent input[type="reset"]:focus,
.simplecontent input[type="reset"]:hover,
.simplecontent input[type="submit"]:focus,
.simplecontent input[type="submit"]:hover {
background: var(--nc-lk-2);
}
.simplecontent code,
.simplecontent kbd,
.simplecontent pre,
.simplecontent samp {
font-family: var(--nc-font-mono);
}
.simplecontent code,
.simplecontent kbd,
.simplecontent pre,
.simplecontent samp {
background: #ddd;
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
padding: 3px 6px;
font-size: 0.9rem;
}
.simplecontent kbd {
border-bottom: 3px solid var(--nc-bg-3);
}
.simplecontent pre {
padding: 1rem 1.4rem;
max-width: 100%;
overflow: auto;
}
.simplecontent pre code {
background: inherit;
font-size: inherit;
color: inherit;
border: 0;
padding: 0;
margin: 0;
}
.simplecontent code pre {
display: inline;
background: inherit;
font-size: inherit;
color: inherit;
border: 0;
padding: 0;
margin: 0;
}
.simplecontent details {
padding: 0.6rem 1rem;
background: #ddd;
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
}
.simplecontent summary {
cursor: pointer;
font-weight: 700;
}
.simplecontent details[open] {
padding-bottom: 0.75rem;
}
.simplecontent details[open] summary {
margin-bottom: 6px;
}
.simplecontent details[open] > :last-child {
margin-bottom: 0;
}
.simplecontent dt {
font-weight: 700;
}
.simplecontent dd::before {
content: "→ ";
}
.simplecontent hr {
border: 0;
border-bottom: 1px solid var(--nc-bg-3);
margin: 1rem auto;
}
.simplecontent fieldset {
margin-top: 1rem;
padding: 2rem;
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
}
.simplecontent legend {
padding: auto 0.5rem;
}
.simplecontent table {
border-collapse: collapse;
width: 100%;
}
.simplecontent td,
.simplecontent th {
border: 1px solid var(--nc-bg-3);
text-align: left;
padding: 0.5rem;
}
.simplecontent th {
background: #ddd;
}
.simplecontent tr:nth-child(even) {
background: #ddd;
}
.simplecontent table caption {
font-weight: 700;
margin-bottom: 0.5rem;
}
.simplecontent textarea {
max-width: 100%;
}
.simplecontent ol,
.simplecontent ul {
padding-left: 2rem;
}
.simplecontent li {
margin-top: 0.4rem;
}
.simplecontent ol ol,
.simplecontent ol ul,
.simplecontent ul ol,
.simplecontent ul ul {
margin-bottom: 0;
}
.simplecontent mark {
padding: 3px 6px;
background: var(--nc-ac-1);
color: var(--nc-ac-tx);
}
.simplecontent input,
.simplecontent select,
.simplecontent textarea {
padding: 6px 12px;
margin-bottom: 0.5rem;
background: #ddd;
color: var(--nc-tx-2);
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
box-shadow: none;
box-sizing: border-box;
}
.simplecontent img {
max-width: 100%;
}
.simplecontent blockquote {
background: #ececec;
}
.simplecontent ol {
list-style-type: decimal;
}
.simplecontent ul {
list-style-type: circle;
}
.simplecontent *{margin:0;padding:0}.simplecontent address,.simplecontent area,.simplecontent article,.simplecontent aside,.simplecontent audio,.simplecontent blockquote,.simplecontent datalist,.simplecontent details,.simplecontent dl,.simplecontent fieldset,.simplecontent figure,.simplecontent form,.simplecontent iframe,.simplecontent img,.simplecontent input,.simplecontent meter,.simplecontent nav,.simplecontent ol,.simplecontent optgroup,.simplecontent option,.simplecontent output,.simplecontent p,.simplecontent pre,.simplecontent progress,.simplecontent ruby,.simplecontent section,.simplecontent table,.simplecontent textarea,.simplecontent ul,.simplecontent video{margin-bottom:1rem}.simplecontent button,.simplecontent html,.simplecontent input,.simplecontent select{font-family:var(--nc-font-sans)}.simplecontent body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:break-word;overflow-wrap:break-word;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}.simplecontent h1,.simplecontent h2,.simplecontent h3,.simplecontent h4,.simplecontent h5,.simplecontent h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}.simplecontent h1,.simplecontent h2,.simplecontent h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}.simplecontent h4,.simplecontent h5,.simplecontent h6{margin-bottom:.3rem}.simplecontent h1{font-size:2.25rem}.simplecontent h2{font-size:1.85rem}.simplecontent h3{font-size:1.55rem}.simplecontent h4{font-size:1.25rem}.simplecontent h5{font-size:1rem}.simplecontent h6{font-size:.875rem}.simplecontent a{color:#3d5af1}.simplecontent a:hover{color:var(--nc-lk-2)}.simplecontent abbr:hover{cursor:help}.simplecontent blockquote{padding:1.5rem;background:#ddd;border-left:5px solid var(--nc-bg-3)}.simplecontent abbr{cursor:help}.simplecontent blockquote :last-child{padding-bottom:0;margin-bottom:0}.simplecontent header{background:#ddd;border-bottom:1px solid var(--nc-bg-3);padding:2rem 1.5rem;margin:-2rem calc(0px - (50vw - 50%)) 2rem;padding-left:calc(50vw - 50%);padding-right:calc(50vw - 50%)}.simplecontent header h1,.simplecontent header h2,.simplecontent header h3{padding-bottom:0;border-bottom:0}.simplecontent header>:first-child{margin-top:0;padding-top:0}.simplecontent header>:last-child{margin-bottom:0}.simplecontent a button,.simplecontent button,.simplecontent input[type=button],.simplecontent input[type=reset],.simplecontent input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:#3d5af1;color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}.simplecontent a button[disabled],.simplecontent button[disabled],.simplecontent input[type=button][disabled],.simplecontent input[type=reset][disabled],.simplecontent input[type=submit][disabled]{cursor:default;opacity:.5;cursor:not-allowed}.simplecontent .button:focus,.simplecontent .button:hover,.simplecontent button:focus,.simplecontent button:hover,.simplecontent input[type=button]:focus,.simplecontent input[type=button]:hover,.simplecontent input[type=reset]:focus,.simplecontent input[type=reset]:hover,.simplecontent input[type=submit]:focus,.simplecontent input[type=submit]:hover{background:var(--nc-lk-2)}.simplecontent code,.simplecontent kbd,.simplecontent pre,.simplecontent samp{font-family:var(--nc-font-mono)}.simplecontent code,.simplecontent kbd,.simplecontent pre,.simplecontent samp{background:#ddd;border:1px solid var(--nc-bg-3);border-radius:4px;padding:3px 6px;font-size:.9rem}.simplecontent kbd{border-bottom:3px solid var(--nc-bg-3)}.simplecontent pre{padding:1rem 1.4rem;max-width:100%;overflow:auto}.simplecontent pre code{background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}.simplecontent code pre{display:inline;background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}.simplecontent details{padding:.6rem 1rem;background:#ddd;border:1px solid var(--nc-bg-3);border-radius:4px}.simplecontent summary{cursor:pointer;font-weight:700}.simplecontent details[open]{padding-bottom:.75rem}.simplecontent details[open] summary{margin-bottom:6px}.simplecontent details[open]>:last-child{margin-bottom:0}.simplecontent dt{font-weight:700}.simplecontent dd::before{content:"→ "}.simplecontent hr{border:0;border-bottom:1px solid var(--nc-bg-3);margin:1rem auto}.simplecontent fieldset{margin-top:1rem;padding:2rem;border:1px solid var(--nc-bg-3);border-radius:4px}.simplecontent legend{padding:auto .5rem}.simplecontent table{border-collapse:collapse;width:100%}.simplecontent td,.simplecontent th{border:1px solid var(--nc-bg-3);text-align:left;padding:.5rem}.simplecontent th{background:#ddd}.simplecontent tr:nth-child(even){background:#ddd}.simplecontent table caption{font-weight:700;margin-bottom:.5rem}.simplecontent textarea{max-width:100%}.simplecontent ol,.sLine truncated
+240 -218
View File
@@ -1,218 +1,240 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
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;
$: processed_last_submit = true;
$: createbtnenabled = is_card_count_valid;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
bulk_modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit_with_print();
}
}
};
})();
function submit_without_print() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("creating-blanco-cards"));
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, true)
.then((result) => {
bulk_modal_open = false;
//
toast.dismiss();
toast.success($_("created-blanco-cards"));
dispatch("created", { cards: result });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
}
}
function submit_with_print() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.dismiss();
toast.loading($_("creating-blanco-cards"));
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, true)
.then((result) => {
bulk_modal_open = false;
//
toast.dismiss();
toast.success($_("created-blanco-cards"));
toast.loading($_("generating-pdf"));
dispatch("created", { cards: result });
documentServer.generateCards(result, "de")
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Bulkcards.pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.dismiss();
toast.success($_("pdf-successfully-generated"));
})
.catch((err) => {
console.error(err);
});
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
}
}
</script>
{#if bulk_modal_open}
<div
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 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 bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 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"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
/></svg
>
</div>
<div class="mt-3 sm:mt-0">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-bulk-blanco-cards")}
</h3>
<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-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="amount"
class="block text-sm font-medium text-gray-700"
>{$_("amount")}</label
>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_card_count_valid}
class:focus:border-red-500={!is_card_count_valid}
class:focus:ring-red-500={!is_card_count_valid}
bind:value={card_count}
type="number"
step="1"
name="amount"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-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"
>{$_("cards")}</span
>
</div>
{#if !is_card_count_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("you-must-create-at-least-one-card-or-cancel")}
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 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"
>
{$_("create-and-generate-pdf")}
</button>
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit_without_print}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-400 text-base font-medium text-white hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create-without-pdf")}
</button>
<button
on:click={() => {
bulk_modal_open = false;
}}
type="button"
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>
</div>
</div>
</div>
</div>
{/if}
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerCardService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let bulk_modal_open;
export let current_cards;
function focus(el) {
el.focus();
}
$: card_count = 0;
$: is_card_count_valid = card_count > 0;
$: processed_last_submit = true;
$: createbtnenabled = is_card_count_valid;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
bulk_modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit_with_print();
}
}
};
})();
function submit_without_print() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("creating-blanco-cards"),
duration: -1,
}).showToast();
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, false)
.then((result) => {
bulk_modal_open = false;
//
Toastify({
text: $_("created-blanco-cards"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
function submit_with_print() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("creating-blanco-cards"),
duration: -1,
}).showToast();
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, true)
.then((result) => {
bulk_modal_open = false;
//
Toastify({
text: $_("created-blanco-cards"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_cards = current_cards.concat(result);
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`${config.baseurl}/documents/cards?&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(result),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Bulkcards.pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
console.error(err);
});
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if bulk_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
bulk_modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w- rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-bulk-blanco-cards')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('just-enter-how-many-you-want-and-the-system-will-create-them')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="amount"
class="block text-sm font-medium text-gray-700">{$_('amount')}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_card_count_valid}
class:focus:border-red-500={!is_card_count_valid}
class:focus:ring-red-500={!is_card_count_valid}
bind:value={card_count}
type="number"
step="1"
name="amount"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="400" />
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">{$_('cards')}</span>
</div>
{#if !is_card_count_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('you-must-create-at-least-one-card-or-cancel')}
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit_with_print}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create-and-generate-pdf')}
</button>
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit_without_print}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-400 text-base font-medium text-white hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create-without-pdf')}
</button>
<button
on:click={() => {
bulk_modal_open = false;
}}
type="button"
class="mr-auto mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}
+170 -191
View File
@@ -1,191 +1,170 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { RunnerCardService, RunnerService } from "@odit/lfk-client-js";
import Select from "svelte-select";
import { createEventDispatcher } from "svelte";
import toast from "svelte-french-toast";
export let modal_open;
const dispatch = createEventDispatcher();
const getRunnerLabel = (option) => {
if (option.middlename) {
return option.firstname + " " + option.middlename + " " + option.lastname;
}
return option.firstname + " " + option.lastname;
};
const filterRunners = (label, filterText, option) => {
if (filterText.startsWith("#")) {
return option.value.id == parseInt(filterText.replace("#", ""));
}
return (
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase())
);
};
function focus(el) {
el.focus();
}
$: runner = 0;
$: enabled = true;
$: processed_last_submit = true;
let loading = true;
let runners = [];
RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r };
});
loading = false;
});
$: createbtnenabled = true;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("adding-card"));
let postdata = {
runner,
enabled,
};
RunnerCardService.runnerCardControllerPost(postdata)
.then((result) => {
runner = 0;
modal_open = false;
//
toast.dismiss();
toast.success($_("card-added"));
dispatch("created", { cards: [result] });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
}
}
</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
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
/></svg
>
</div>
<div class="mt-3 sm:mt-0">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-card")}
</h3>
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_("you-can-provide-a-runner-but-you-dont-have-to")}
{$_(
"if-you-want-to-create-multiple-blanco-cards-try-the-add-bulk-button"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700"
>{$_("runner")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)}
items={runners}
bind:loading
showChevron={!loading}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
on:select={(selectedValue) =>
(runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)}
/>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 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"
>
{$_("create")}
</button>
<button
on:click={() => {
modal_open = false;
}}
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>
</div>
</div>
</div>
{/if}
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
RunnerCardService,
RunnerService,
ScanService,
} from "@odit/lfk-client-js";
import Select from "svelte-select";
import Toastify from "toastify-js";
export let modal_open;
export let current_cards;
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase());
function focus(el) {
el.focus();
}
$: runner = 0;
$: runners = [];
$: enabled = true;
$: processed_last_submit = true;
RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r };
});
});
$: createbtnenabled = true;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("adding-card"),
duration: -1,
}).showToast();
let postdata = {
runner,
enabled,
};
RunnerCardService.runnerCardControllerPost(postdata)
.then((result) => {
runner = 0;
modal_open = false;
//
Toastify({
text: $_("card-added"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_cards.push(result);
current_cards = current_cards;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-card')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('you-can-provide-a-runner-but-you-dont-have-to')}
{$_('if-you-want-to-create-multiple-blanco-cards-try-the-add-bulk-button')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700">{$_('runner')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterRunners(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_('search-for-runner-by-name-or-id')}
noOptionsMessage={$_('no-runners-found')}
on:select={(selectedValue) => (runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)} />
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}
+186 -200
View File
@@ -1,200 +1,186 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { RunnerCardService, RunnerService } from "@odit/lfk-client-js";
import Select from "svelte-select";
import { createEventDispatcher } from "svelte";
import toast from "svelte-french-toast";
export let edit_modal_open;
export let runner = {};
export let editable = {};
export let original_data = {};
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) => {
if (filterText.startsWith("#")) {
return option.value.id == parseInt(filterText.replace("#", ""));
}
return (
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase())
);
};
function focus(el) {
el.focus();
}
$: runners = [];
$: enabled = true;
$: processed_last_submit = true;
const dispatch = createEventDispatcher();
RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r };
});
});
$: createbtnenabled = !(
JSON.stringify(editable) === JSON.stringify(original_data)
);
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
edit_modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("updating-card"));
RunnerCardService.runnerCardControllerPut(original_data.id, editable)
.then((result) => {
runner = {};
editable = {};
original_data = {};
edit_modal_open = false;
//
toast.dismiss();
toast.success($_("card-updated"));
dispatch("dataUpdated", { card: result });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
}
}
</script>
{#if edit_modal_open}
<div
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 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"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
/></svg
>
</div>
<div class="mt-3 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="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-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="runner"
class="block text-sm font-medium text-gray-700"
>{$_("runner")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
bind:selectedValue={runner}
on:select={(selectedValue) =>
(editable.runner = selectedValue.detail.value.id)}
on:clear={() => (editable.runner = null)}
/>
</div>
<div class="col-span-6">
<p class="text-gray-500">
<input
id="enabled"
on:change={() => {
editable.enabled = !editable.enabled;
}}
name="enabled"
type="checkbox"
checked={editable.enabled}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
{$_("this-card-is")}
{#if editable.enabled}
{$_("enabled")}
{:else}{$_("disabled")}{/if}
</p>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 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"
>
{$_("save-changes")}
</button>
<button
on:click={() => {
edit_modal_open = false;
}}
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>
</div>
</div>
</div>
{/if}
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerCardService, RunnerService } from "@odit/lfk-client-js";
import Select from "svelte-select";
import Toastify from "toastify-js";
export let edit_modal_open;
export let current_cards;
export let runner = {};
export let editable = {};
export let original_data = {};
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase());
function focus(el) {
el.focus();
}
$: runners = [];
$: enabled = true;
$: processed_last_submit = true;
RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r };
});
});
$: createbtnenabled = !(
JSON.stringify(editable) === JSON.stringify(original_data)
);
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
edit_modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("updating-card"),
duration: -1,
}).showToast();
RunnerCardService.runnerCardControllerPut(original_data.id, editable)
.then((result) => {
let id = original_data.id;
runner = {};
editable = {};
original_data = {};
edit_modal_open = false;
//
Toastify({
text: $_("card-updated"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_cards[current_cards.findIndex((c) => c.id === id)] = result;
current_cards = current_cards;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if edit_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
edit_modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('edit-a-card')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('you-can-provide-a-runner-but-you-dont-have-to')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="runner"
class="block text-sm font-medium text-gray-700">{$_('runner')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterRunners(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_('search-for-runner-by-name-or-id')}
noOptionsMessage={$_('no-runners-found')}
bind:selectedValue={runner}
on:select={(selectedValue) => (editable.runner = selectedValue.detail.value.id)}
on:clear={() => (editable.runner = null)} />
</div>
<div class="col-span-6">
<p class="text-gray-500">
<input
id="enabled"
on:change={() => {
editable.enabled = !editable.enabled;
}}
name="enabled"
type="checkbox"
checked={editable.enabled}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
{$_('this-card-is')}
{#if editable.enabled}
{$_('enabled')}
{:else}{$_('disabled')}{/if}
</p>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('save-changes')}
</button>
<button
on:click={() => {
edit_modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}
-16
View File
@@ -1,16 +0,0 @@
<script>
import { _ } from "svelte-i18n";
export let runner;
</script>
{#if !runner}
{$_("non-blanko")}
{:else}
<a href={`/runners/${runner.id}`}>
{#if runner.middlename}
{runner.firstname} {runner.middlename} {runner.lastname}
{:else}
{runner.firstname} {runner.lastname}
{/if}
</a>
{/if}
-16
View File
@@ -1,16 +0,0 @@
<script>
import { _ } from "svelte-i18n";
export let enabled = false;
</script>
{#if enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full 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 border border-current bg-red-100 text-red-800"
>{$_("disabled")}</span
>
{/if}
+40 -53
View File
@@ -1,53 +1,40 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddCardBulkModal from "./AddCardBulkModal.svelte";
import AddCardModal from "./AddCardModal.svelte";
import CardsOverview from "./CardsOverview.svelte";
$: current_cards = [];
export let modal_open = false;
export let bulk_modal_open = false;
let addCards;
</script>
<section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("cards")}
</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>
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:CREATE")}
<AddCardModal
bind:modal_open
on:created={(event) => {
addCards(event.detail.cards);
}}
/>
<AddCardBulkModal
bind:bulk_modal_open
on:created={(event) => {
addCards(event.detail.cards);
}}
/>
{/if}
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddCardBulkModal from "./AddCardBulkModal.svelte";
import AddCardModal from "./AddCardModal.svelte";
import CardsOverview from "./CardsOverview.svelte";
$: current_cards = [];
export let modal_open = false;
export let bulk_modal_open = false;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('cards')}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:CREATE')}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('add-card')}
</button>
<button
on:click={() => {
bulk_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create-bulk-cards')}
</button>
{/if}
</span>
<CardsOverview bind:current_cards />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:CREATE')}
<AddCardModal bind:current_cards bind:modal_open />
<AddCardBulkModal bind:current_cards bind:bulk_modal_open />
{/if}
+12 -12
View File
@@ -1,12 +1,12 @@
<script>
import { _ } from "svelte-i18n";
import cards_empty from "./cards.svg";
</script>
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="m-auto 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>
</div>
<script>
import { _ } from "svelte-i18n";
import cards_empty from "./cards.svg";
</script>
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="m-auto" style="height:15rem" src={cards_empty} alt="" />
<span class="font-bold">{$_('there-are-no-cards-yet')}</span><br />
<span>{$_('add-your-first-card')}</span>
</p>
</div>
+281 -316
View File
@@ -1,316 +1,281 @@
<script>
import { _ } from "svelte-i18n";
import { RunnerCardService } from "@odit/lfk-client-js";
import store from "../../store";
import toast from "svelte-french-toast";
import CardsEmptyState from "./CardsEmptyState.svelte";
import CardDetailModal from "./CardDetailModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import InputElement from "../shared/InputElement.svelte";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { writable } from "svelte/store";
import TableBottom from "../shared/TableBottom.svelte";
import TableActions from "../shared/TableActions.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import CardStatus from "./CardStatus.svelte";
import CardRunner from "./CardRunner.svelte";
import { onMount } from "svelte";
import { runnerFilter, statusFilter } from "../shared/tablefilters";
import DeleteCardModal from "./DeleteCardModal.svelte";
export let edit_modal_open = false;
export let runner = {};
export let editable = {};
export let original_data = {};
export let current_cards = [];
export const addCards = (cards) => {
current_cards = current_cards.concat(...cards);
options.update((options) => ({
...options,
data: current_cards,
}));
};
$: dataLoaded = false;
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: selectedCards =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: active_delete = undefined;
$: cards_show = generate_cards.length > 0;
$: generate_cards = [];
const columns = [
{
accessorKey: "code",
header: () => $_("code"),
filterFn: `includesString`,
},
{
accessorKey: "runner",
header: () => $_("runner"),
cell: (info) => {
return renderComponent(CardRunner, { runner: info.getValue() });
},
filterFn: `runner`,
},
{
accessorKey: "enabled",
cell: (info) => {
return renderComponent(CardStatus, { enabled: info.getValue() });
},
header: () => $_("status"),
filterFn: `status`,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsAction: () => {
open_edit_modal(
current_cards[
current_cards.findIndex((r) => r.id == info.row.original.id)
]
);
},
deleteAction: () => {
active_delete =
current_cards[
current_cards.findIndex((r) => r.id == info.row.original.id)
];
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes("CARD:DELETE"),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
filterFns: {
runner: runnerFilter,
status: statusFilter,
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
function open_edit_modal(card) {
const getRunnerLabel = (option) =>
option.firstname +
" " +
(option.middlename || "") +
" " +
option.lastname;
if (card.runner?.id) {
runner = Object.assign(
{ runner },
{ label: getRunnerLabel(card.runner), value: card.runner }
);
card.runner = card.runner.id;
} else {
card.runner = null;
runner = null;
}
editable = Object.assign(editable, card);
original_data = Object.assign(original_data, card);
edit_modal_open = true;
}
async function deleteCard(delete_card_id) {
await RunnerCardService.runnerCardControllerRemove(delete_card_id, true);
current_cards = current_cards.filter((r) => r.id !== delete_card_id);
options.update((options) => ({
...options,
data: current_cards,
}));
toast.success($_("card-deleted"));
}
onMount(async () => {
let page = 0;
let pagesize = 500;
while (page >= 0) {
const cards = await RunnerCardService.runnerCardControllerGetAll(
page,
pagesize
);
if (cards.length == 0) {
page = -2;
}
current_cards = current_cards.concat(...cards);
options.update((options) => ({
...options,
data: current_cards,
}));
dataLoaded = true;
page++;
}
});
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:UPDATE")}
<CardDetailModal
bind:edit_modal_open
bind:runner
bind:editable
bind:original_data
on:dataUpdated={(event) => {
current_cards[
current_cards.findIndex((c) => c.id === event.detail.card.id)
] = event.detail.card;
current_cards = current_cards;
options.update((options) => ({
...options,
data: current_cards,
}));
}}
/>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")}
<DeleteCardModal
delete_card={active_delete}
modal_open={active_delete != undefined}
on:delete={(event) => {
deleteCard(event.detail.id);
}}
/>
{#if !dataLoaded}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("loading-cards")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:else if current_cards.length === 0}
<CardsEmptyState />
{:else}
<div class="h-12 mt-1">
{#if selected.length > 0}
<button
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
on:click={async () => {
const prom = [];
for (const card of selectedCards) {
prom.push(
await RunnerCardService.runnerCardControllerRemove(
card.id,
true
)
);
}
await Promise.all(prom);
for (const card of selectedCards) {
current_cards = current_cards.filter((r) => r.id !== card.id);
}
options.update((options) => ({
...options,
data: current_cards,
}));
$table.resetRowSelection();
}}
>
{$_("delete-cards")}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
{/if}
<GenerateRunnerCards
cards_show={selected.length > 0}
bind:generate_cards={selectedCards}
/>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead 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>
<TableBottom {table} {selected} />
{/if}
{/if}
<style>
table tbody tr td:nth-child(2) {
font-family: monospace;
}
</style>
<script>
import { _ } from "svelte-i18n";
import { RunnerCardService } from "@odit/lfk-client-js";
import store from "../../store";
import Toastify from "toastify-js";
import CardsEmptyState from "./CardsEmptyState.svelte";
import CardDetailModal from "./CardDetailModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
export let edit_modal_open = false;
export let runner = {};
export let editable = {};
export let original_data = {};
export let current_cards = [];
$: filtered_cards = current_cards.filter(function (c) {
if (
c.code.toLowerCase().includes(searchvalue_lowercase) ||
c.runner?.firstname.toLowerCase().includes(searchvalue_lowercase) ||
c.runner?.middlename.toLowerCase().includes(searchvalue_lowercase) ||
c.runner?.lastname.toLowerCase().includes(searchvalue_lowercase) ||
should_display_based_on_id(c.id)
) {
return true;
}
});
$: searchvalue = "";
$: searchvalue_lowercase = searchvalue.toLowerCase();
$: active_deletes = [];
$: cards_show = current_cards.some((r) => r.is_selected === true);
$: generate_cards = current_cards.filter((r) => r.is_selected === true);
const cards_promise = RunnerCardService.runnerCardControllerGetAll().then(
(val) => {
current_cards = val;
}
);
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
}
const getRunnerLabel = (option) =>
option?.firstname + " " + (option?.middlename || "") + " " + (option?.lastname || "{$_('non-blanko')}");
function open_edit_modal(card) {
if(card.runner?.id){
runner = Object.assign(
{ runner },
{ label: getRunnerLabel(card.runner), value: card.runner }
);
card.runner = card.runner.id;
}
else{
card.runner=null;
runner = null
}
editable = Object.assign(editable, card);
original_data = Object.assign(original_data, card);
edit_modal_open = true;
}
// -----------------
let scrollTop = 0;
$: rendered = filtered_cards;
let innerHeight = 0;
let ele;
$: updateSlice(scrollTop);
$: innerHeight = `${filtered_cards.length * 25}px`;
$: if (ele) updateSlice();
function updateSlice() {
const height = ele ? parseInt(ele.clientHeight) : 100;
const init = scrollTop / 25;
const end = Math.ceil((scrollTop + height) / 25);
rendered = filtered_cards.slice(init, end + 15);
}
function updateScroll($event) {
scrollTop = $event.target.scrollTop;
}
</script>
<style>
table tbody {
display: block;
overflow-y: scroll;
}
table thead, table tbody tr {
display: table;
width: 100%;
table-layout: fixed;
}
</style>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:UPDATE')}
<CardDetailModal
bind:current_cards
bind:edit_modal_open
bind:runner
bind:editable
bind:original_data />
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
{#await cards_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">{$_('loading-cards')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_cards.length === 0}
<CardsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div class="h-12">
<GenerateRunnerCards
bind:cards_show
bind:generate_cards />
</div>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span
on:click={() => {
const newstate = !current_cards.some((r) => r.is_selected === true);
current_cards = current_cards.map((r) => {
r.is_selected = newstate;
return r;
});
}}
class="underline cursor-pointer select-none">{#if current_cards.some((r) => r.is_selected === true)}
{$_('deselect-all')}
{:else}{$_('select-all')}{/if}
</span>
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('code')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('runner')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('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 virtual-wrapper"
on:scroll={updateScroll}
style="height: 70vh; width:100%"
bind:this={ele}
>
{#each filtered_cards as card, index}
{#if card.code
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || card.runner?.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || card.runner?.middlename
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || card.runner?.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(card.id)}
<tr data-rowid="card_{card.id}">
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={card.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">{card.code}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if card.runner}
<a
href="../runners/{card.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{card.runner.firstname}
{card.runner.middlename || ''}
{card.runner.lastname}</a>
{:else}{$_('non-blanko')}{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if card.enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('enabled')}</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('disabled')}</span>
{/if}
</div>
</td>
{#if active_deletes[card.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[card.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
RunnerCardService.runnerCardControllerRemove(card.id, false).then(
(resp) => {
current_cards = current_cards.filter(
(obj) => obj.id !== card.id
);
Toastify({
text: $_('card-deleted'),
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
open_edit_modal(card);
}}
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</button>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:DELETE')}
<button
on:click={() => {
active_deletes[card.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}
-123
View File
@@ -1,123 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_card = {
id: 0,
code: "",
runner: {
firstname: "",
lastname: "",
},
};
const dispatch = createEventDispatcher();
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
async function submit() {
dispatch("delete", { id: delete_card.id });
modal_open = false;
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-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
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
/></svg
>
</div>
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("please-confirm-the-deletion-of-card")}
</h3>
<div class="w-full">
{$_("card")} #{delete_card.code}<br />
<span class="inline-block">
{$_("runner")}:
{#if delete_card.runner}
<span class="inline-block"
>{delete_card.runner.firstname}
{delete_card.runner.lastname}</span
>
{:else}
{$_("non-blanko")}
{/if}</span
>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 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-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>
<button
on:click={() => {
modal_open = false;
}}
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>
</div>
</div>
</div>
{/if}
+84 -128
View File
@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
GroupContactService,
RunnerTeamService,
@@ -9,7 +9,7 @@
} from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail";
import isMobilePhone from "validator/es/lib/isMobilePhone";
import toast from "svelte-french-toast";
import Toastify from "toastify-js";
export let modal_open;
export let current_contacts;
$: selected_team = [];
@@ -43,7 +43,7 @@
$: address_zipcode_value = "";
$: address_city_value = "";
$: processed_last_submit = true;
$: address_checked = false;
$: address_checked = true;
$: isPhoneValidOrEmpty =
(phone_input_value.includes("+") &&
isMobilePhone(
@@ -85,7 +85,10 @@
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("contact-is-being-added"));
const toast = Toastify({
text: "Contact is being added...",
duration: -1,
}).showToast();
let address = {};
if (address_checked === true) {
address = {
@@ -119,8 +122,11 @@
email_input_value = "";
modal_open = false;
//
toast.dismiss();
toast.success($_("contact-added"));
Toastify({
text: "Contact added",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_contacts.push(result);
current_contacts = current_contacts;
})
@@ -129,6 +135,8 @@
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
@@ -136,71 +144,59 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
}}>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
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 rounded-t-xl">
<div class="">
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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z"
/></svg
>
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg>
</div>
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-contact")}
{$_('create-a-new-contact')}
</h3>
<div class="mb-6">
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-required-information-to-add-a-new-contact"
)}
{$_('please-provide-the-required-information-to-add-a-new-contact')}
</p>
</div>
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700"
>{$_("first-name")}</label
>
class="block text-sm font-medium text-gray-700">{$_('first-name')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_("first-name")}
placeholder={$_('first-name')}
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
@@ -208,41 +204,34 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("first-name-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700"
>{$_("middle-name")}</label
>
class="block text-sm font-medium text-gray-700">{$_('middle-name')}</label>
<input
autocomplete="off"
placeholder={$_("middle-name")}
placeholder={$_('middle-name')}
bind:value={middlename_input_value}
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="lastname"
class="block text-sm font-medium text-gray-700"
>{$_("last-name")}</label
>
class="block text-sm font-medium text-gray-700">{$_('last-name')}</label>
<input
autocomplete="off"
placeholder={$_("last-name")}
placeholder="{$_('last-name')}"
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
@@ -250,28 +239,23 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("last-name-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="team"
class="block text-sm font-medium text-gray-700"
>{$_("teams")}</label
>
class="block text-sm font-medium text-gray-700">{$_('teams')}</label>
<select
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-neutral-800 rounded-md p-2"
>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
{#each teams as team}
<option value={team.id}>
{team.parentGroup.name}
@@ -287,12 +271,10 @@
<div class="col-span-6">
<label
for="phone"
class="block text-sm font-medium text-gray-700"
>{$_("phone")}</label
>
class="block text-sm font-medium text-gray-700">{$_('phone')}</label>
<input
autocomplete="off"
placeholder={$_("phone")}
placeholder={$_('phone')}
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
@@ -300,27 +282,21 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{@html $_(
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number"
)}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="email"
class="block text-sm font-medium text-gray-700"
>{$_("e-mail-adress")}</label
>
class="block text-sm font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input
autocomplete="off"
placeholder={$_("e-mail-adress")}
placeholder={$_('e-mail-adress')}
class:border-red-500={!isEmailValidOrEmpty}
class:focus:border-red-500={!isEmailValidOrEmpty}
class:focus:ring-red-500={!isEmailValidOrEmpty}
@@ -328,13 +304,11 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-email-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-email-is-required')}
</span>
{/if}
</div>
@@ -345,25 +319,22 @@
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-semibold text-gray-700"
>{$_("address")}</label
>
<label
for="comments"
class="font-medium text-gray-700">{$_('address')}</label>
</div>
</div>
{#if address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700"
>{$_("address")}</label
>
class="block text-sm font-medium text-gray-700">{$_('address')}</label>
<input
autocomplete="off"
placeholder={$_("address")}
placeholder="{$_('address')}"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
@@ -371,41 +342,34 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("address-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('address-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700"
>{$_("apartment-suite-etc")}</label
>
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label>
<input
autocomplete="off"
placeholder={$_("apartment-suite-etc")}
placeholder={$_('apartment-suite-etc')}
bind:value={address_input2_value}
bind:this={address_input2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700"
>{$_("zip-postal-code")}</label
>
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label>
<input
autocomplete="off"
placeholder={$_("zip-postal-code")}
placeholder={$_('zip-postal-code')}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
@@ -413,25 +377,21 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-zipcode-postal-code-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-zipcode-postal-code-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700"
>{$_("city")}</label
>
class="block text-sm font-medium text-gray-700">{$_('city')}</label>
<input
autocomplete="off"
placeholder={$_("city")}
placeholder="{$_('city')}"
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
@@ -439,13 +399,11 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-city-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-city-is-required')}
</span>
{/if}
</div>
@@ -454,24 +412,22 @@
</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">
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create")}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="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")}
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
+387 -392
View File
@@ -1,399 +1,394 @@
<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 Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail";
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) {
Toastify({
text: $_("contact-is-being-updated"),
duration: 2500,
}).showToast();
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;
Toastify({
text: $_("updated-contact"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.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">
<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>
<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>
{:catch error}
<PromiseError {error} />
<PromiseError {error} />
{/await}
+14 -15
View File
@@ -8,23 +8,22 @@
</script>
<section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("contacts")}
</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}
<span 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>
<ContactsOverview bind:current_contacts />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:CREATE")}
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:CREATE')}
<AddContactModal bind:current_contacts bind:modal_open />
{/if}
@@ -9,8 +9,8 @@
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={team_empty} alt="" />
<span class="font-bold">{$_("there-are-no-contacts-added-yet")}</span><br />
<span>{$_("add-your-first-contact")}</span>
<span class="font-bold">{$_('there-are-no-contacts-added-yet')}</span><br />
<span>{$_('add-your-first-contact')}</span>
</p>
</div>
+173 -194
View File
@@ -1,198 +1,177 @@
<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 Toastify from "toastify-js";
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";
$: 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-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 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="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('name')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('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 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={() => {
GroupContactService.groupContactControllerRemove(t.id, false).then(
(resp) => {
current_contacts = current_contacts.filter(
(obj) => obj.id !== t.id
);
Toastify({
text: $_('contact-deleted'),
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{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}
+341 -439
View File
@@ -1,439 +1,341 @@
<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("/");
}
</script>
<section class="min-h-screen bg-gray-50">
<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")}
<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);
}
}
</style>
<script>
import { _ } from "svelte-i18n";
import localForage from "localforage";
import store from "../../store";
import { router } from "tinro";
import NoComponentLoaded from "../base/NoComponentLoaded.svelte";
import { AuthService } from "@odit/lfk-client-js";
$: navOpen = false;
function logout() {
localForage.clear();
location.replace("/");
}
</script>
<style>
.collapsed_navigation {
transform: translateX(-100%);
}
@media (min-width: 768px) {
.collapsed_navigation {
transform: translateX(0px);
}
}
</style>
<section class="min-h-screen bg-gray-50">
<div
class:collapsed_navigation={!navOpen}
class="select-none fixed top-0 left-0 z-20 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 bg-gray-50">
<a href="/" class="flex items-center px-4 py-5">
<img src="/lfk-logo.png" alt="Logo" class="h-10" />
<h3 class="text-lg">Lauf für Kaya! Admin</h3>
</a>
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation">
<a
class:bg-gray-100={$router.path === '/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" />
</svg>
<span>{$_('dashboard-title')}</span>
</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
<a
class:bg-gray-100={$router.path.includes('/orgs/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/orgs/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg>
<span>{$_('orgs')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')}
<a
class:bg-gray-100={$router.path === '/users/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/users/">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 14v8H4a8 8 0 018-8zm0-1a6 6 0 110-12 6 6 0 010 12zm2.6 5.81a3.51 3.51 0 010-1.62l-1-.57 1-1.74 1 .58a3.5 3.5 0 011.4-.82V13.5h2v1.15a3.5 3.5 0 011.4.8l1-.57 1 1.74-1 .57a3.51 3.51 0 010 1.62l1 .57-1 1.74-1-.58a3.5 3.5 0 01-1.4.82v1.14h-2v-1.15a3.5 3.5 0 01-1.4-.8l-1 .57-1-1.74 1-.57zM18 17a1 1 0 100 2 1 1 0 000-2z" /></svg>
<span>{$_('users')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')}
<a
class:bg-gray-100={$router.path === '/groups/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/groups/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('user-groups')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
<a
class:bg-gray-100={$router.path === '/runners/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/runners/">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
<span>{$_('runners')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
<a
class:bg-gray-100={$router.path === '/teams/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/teams/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('teams')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
<a
class:bg-gray-100={$router.path.includes('/donors/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donors/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg>
<span>{$_('donors')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
<a
class:bg-gray-100={$router.path.includes('/donations/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donations/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg>
<span>{$_('donations')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')}
<a
class:bg-gray-100={$router.path === '/tracks/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/tracks/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" /></svg>
<span>{$_('tracks')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
<a
class:bg-gray-100={$router.path === '/cards/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/cards/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
<span>{$_('cards')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')}
<a
class:bg-gray-100={$router.path === '/scans/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/scans/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg>
<span>Scans</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')}
<a
class:bg-gray-100={$router.path === '/contacts/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/contacts/">
<svg
fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg>
<span>{$_('contacts')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')}
<a
class:bg-gray-100={$router.path === '/scanstations/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/scanstations/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
<span>{$_('scanstations')}</span>
</a>
{/if}
<a
class:bg-gray-100={$router.path === '/settings/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/settings/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
fill-rule="evenodd"
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
clip-rule="evenodd" />
</svg>
<span>{$_('settings')}</span>
</a>
<a
class:bg-gray-100={$router.path === '/about/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/about/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
<path d="M12 16v-4M12 8h.01" /></svg>
<span>{$_('about')}</span>
</a>
<span
tabindex="0"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
on:click={() => {
AuthService.authControllerLogout();
logout();
}}>
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z" /></svg>
<span>{$_('logout')}</span>
</span>
</nav>
</div>
<div class="ml-0 transition md:ml-60">
<header
class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden">
<button
on:click={() => {
navOpen = true;
}}
class="block btn btn-light md:hidden">
<span class="sr-only">Menu</span><svg
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentcolor"><path
fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clip-rule="evenodd" /></svg></button>
</header>
<slot>
<NoComponentLoaded />
</slot>
</div>
{#if navOpen === true}
<div
on:click={() => {
navOpen = false;
console.log({ navOpen });
}}
class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" />
{/if}
</section>
+18 -259
View File
@@ -1,263 +1,22 @@
<script>
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();
import { _ } from "svelte-i18n";
import StatCards from "./StatCards.svelte";
import store from "../../store";
let navOpen = false;
</script>
<div class="p-2 md:p-5 overflow-x-hidden">
<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
class="p-5 overflow-x-hidden"
on:click={() => {
navOpen = false;
}}>
<h1 class="text-3xl leading-tight">
<span class="font-extrabold">{$_('dashboard-title')}</span>
<span>
-
{$_('dashboard-greeting')},
<span
class="text-blue-500">{store.state.jwtinfo.userdetails.firstname} {store.state.jwtinfo.userdetails.lastname}</span></span>
</h1>
<StatCards />
</div>
-21
View File
@@ -1,21 +0,0 @@
<script>
import { _ } from "svelte-i18n";
export let href = "#";
export let title = "";
export let value = "";
</script>
<a {href}>
<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-md sm:text-xs uppercase font-normal text-grey-500">
{title}
</div>
<div class="text-2xl sm:text-xl font-bold font-mono">{value}</div>
</div>
<slot />
</div>
</div>
</a>
+165
View File
@@ -0,0 +1,165 @@
<script>
import { StatsService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
const stats_promise = StatsService.statsControllerGet();
</script>
<!-- -->
<h1>{$_('general-stats')}</h1>
{#await stats_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('stats-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then stats}
<div
class="flex flex-col lg:flex-row w-full lg:space-x-2 space-y-2 lg:space-y-0 mb-2 lg:mb-4">
<a href="/runners/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('runners')}
</div>
<div class="text-xl font-bold">{stats.total_runners}</div>
</div>
<svg
height="24"
width="24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z" /></svg>
</div>
</div>
</a>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-scans')}
</div>
<div class="text-xl font-bold">{stats.total_scans}</div>
</div><svg
stroke="currentColor"
fill="currentColor"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"><polyline
points="22 12 18 12 15 21 9 3 6 12 2 12" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-donations')}
</div>
<div class="text-xl font-bold">{stats.total_donation} €</div>
</div><svg
xmlns="http://www.w3.org/2000/svg"
height="24"
fill="currentColor"
width="24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M15 18.5A6.48 6.48 0 019.24 15H15v-2H8.58c-.05-.33-.08-.66-.08-1s.03-.67.08-1H15V9H9.24A6.491 6.491 0 0115 5.5c1.61 0 3.09.59 4.23 1.57L21 5.3A8.955 8.955 0 0015 3c-3.92 0-7.24 2.51-8.48 6H3v2h3.06a8.262 8.262 0 000 2H3v2h3.52c1.24 3.49 4.56 6 8.48 6 2.31 0 4.41-.87 6-2.3l-1.78-1.77c-1.13.98-2.6 1.57-4.22 1.57z" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-distance')}
</div>
<div class="text-xl font-bold">
{stats.total_distance / 1000}
km
</div>
</div>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
height="24"
width="24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z" /></svg>
</div>
</div>
</div>
<a href="/teams/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('count_teams')}
</div>
<div class="text-xl font-bold">{stats.total_teams}</div>
</div>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"><path
d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>
</div>
</div>
</a>
<a href="/orgs/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('count_organizations')}
</div>
<div class="text-xl font-bold">{stats.total_orgs}</div>
</div>
<svg
height="24"
fill="currentColor"
width="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M17 11V3H7v4H3v14h8v-4h2v4h8V11h-4zM7 19H5v-2h2v2zm0-4H5v-2h2v2zm0-4H5V9h2v2zm4 4H9v-2h2v2zm0-4H9V9h2v2zm0-4H9V5h2v2zm4 8h-2v-2h2v2zm0-4h-2V9h2v2zm0-4h-2V5h2v2zm4 12h-2v-2h2v2zm0-4h-2v-2h2v2z" /></svg>
</div>
</div>
</a>
</div>
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
+188 -233
View File
@@ -1,28 +1,39 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
DonationService,
DonorService,
RunnerService,
} from "@odit/lfk-client-js";
import Select from "svelte-select";
import { createEventDispatcher, onMount } from "svelte";
import toast from "svelte-french-toast";
import Toastify from "toastify-js";
export let modal_open;
const dispatch = createEventDispatcher();
export let current_donations;
const getDonorLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterDonors = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase());
function focus(el) {
el.focus();
}
$: donor = 0;
$: runner = 0;
$: donors = [];
$: runners = [];
$: is_fixed = false;
$: is_paid = false;
DonorService.donorControllerGetAll().then((val) => {
donors = val.map((r) => {
return { label: getDonorLabel(r), value: r };
});
});
RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => {
return { label: getDonorLabel(r), value: r };
});
});
$: amount_input = 0;
$: processed_last_submit = true;
$: is_amount_valid = amount_input > 0;
@@ -45,16 +56,15 @@
if (processed_last_submit === true) {
let amount_cent = Math.floor(amount_input * 100);
processed_last_submit = false;
toast.loading($_("adding-donation"));
const toast = Toastify({
text: "adding donation",
duration: -1,
}).showToast();
if (is_fixed) {
let postdata = {
donor,
amount: amount_cent,
paidAmount: 0,
};
if (is_paid) {
postdata.paidAmount = amount_cent;
}
DonationService.donationControllerPostFixed(postdata)
.then((result) => {
donor = donors[0].id || 0;
@@ -62,15 +72,21 @@
amount_input = 0;
modal_open = false;
//
toast.dismiss();
toast.success($_("donation_added"));
dispatch("created", { donations: [result] });
Toastify({
text: "donation_added",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donations.push(result);
current_donations = current_donations;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
} else {
let postdata = {
@@ -85,237 +101,29 @@
amount_input = 0;
modal_open = false;
//
toast.dismiss();
toast.success($_("donation_added"));
dispatch("created", { donations: [result] });
Toastify({
text: "donation_added",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donations.push(result);
current_donations = current_donations;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
}
onMount(async () => {
donors = (await DonorService.donorControllerGetAll()).map(
(r) => {
return { label: getDonorLabel(r), value: r };
}
);
runners = (await RunnerService.runnerControllerGetAll()).map(
(r) => {
return { label: getDonorLabel(r), value: r };
}
);
});
</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
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</div>
<div class="mt-3">
<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="text-base" class:text-gray-300={is_fixed}
>{$_("distance-donation")}</span
>
<input
class="toggle relative w-10 h-5 transition-all duration-200 ease-in-out bg-gray-400 rounded-full shadow-inner outline-none appearance-none align-middle"
type="checkbox"
bind:checked={is_fixed}
/>
<span class="ml-2 text-base" class:text-gray-300={!is_fixed}
>{$_("fixed-donation")}</span
>
</label>
<div class="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-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700"
>{$_("donor")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={donors}
showChevron={true}
placeholder={$_("search-for-donor-name-or-id")}
noOptionsMessage={$_("no-donors-found")}
on:select={(selectedValue) =>
(donor = selectedValue.detail.value.id)}
on:clear={() => (donors = null)}
/>
</div>
{#if !is_fixed}
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700"
>{$_("runner")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
on:select={(selectedValue) =>
(runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)}
/>
</div>
{/if}
<div class="col-span-6">
<label
for="donation_amount_eur"
class="block text-sm font-medium text-gray-700"
>
{#if !is_fixed}
{$_("amount-per-kilometer")}
{:else}{$_("donation-amount")}{/if}</label
>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-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 text-sm"
>€</span
>
</div>
{#if !is_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("donation-amount-must-be-greater-that-0-00eur")}
</span>
{/if}
</div>
{#if is_fixed}
<div class="col-span-6">
<label
for="paid"
class="block text-sm font-medium text-gray-700"
>{$_("already-paid")}</label
>
<p class="text-gray-500">
<input
id="paid"
bind:checked={is_paid}
name="paid"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
<span class="align-text-bottom">
{#if is_paid}
{$_("paid")}
{:else}
{$_("open")}
{/if}
</span>
</p>
</div>
{/if}
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 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"
>
{$_("create")}
</button>
<button
on:click={() => {
modal_open = false;
}}
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>
</div>
</div>
</div>
{/if}
<style>
.toggle:before {
input:before {
content: "";
position: absolute;
width: 1.25rem;
@@ -329,12 +137,159 @@
transition: 0.2s ease-in-out;
}
.toggle:checked {
input:checked {
/* @apply: bg-indigo-400; */
background-color: #7f9cf5;
}
.toggle:checked:before {
input:checked:before {
left: 1.25rem;
}
</style>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{#if is_fixed}
{$_('create-a-new-fixed-donation')}
{:else}{$_('create-a-new-distance-donation')}{/if}
</h3>
<label class="content-center align-middle object-center">
<span
class="ml-2 text-base"
class:text-gray-300={is_fixed}>{$_('distance-donation')}</span>
<input
class="relative w-10 h-5 transition-all duration-200 ease-in-out bg-gray-400 rounded-full shadow-inner outline-none appearance-none align-middle"
type="checkbox"
bind:checked={is_fixed} />
<span
class="ml-2 text-base "
class:text-gray-300={!is_fixed}>{$_('fixed-donation')}</span>
</label>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('please-provide-the-nessecary-information-to-create-a-new-donation')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700">{$_('donor')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)}
items={donors}
showChevron={true}
placeholder={$_('search-for-donor-name-or-id')}
noOptionsMessage={$_('no-donors-found')}
on:select={(selectedValue) => (donor = selectedValue.detail.value.id)}
on:clear={() => (donors = null)} />
</div>
{#if !is_fixed}
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700">{$_('runner')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_('search-for-runner-by-name-or-id')}
noOptionsMessage={$_('no-runners-found')}
on:select={(selectedValue) => (runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)} />
</div>
{/if}
<div class="col-span-6">
<label
for="donation_amount_eur"
class="block text-sm font-medium text-gray-700">
{#if !is_fixed}
{$_('amount-per-kilometer')}
{:else}{$_('donation-amount')}{/if}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="2.00" />
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">€</span>
</div>
{#if !is_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('donation-amount-must-be-greater-that-0-00eur')}
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}
@@ -1,205 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { DonationService } from "@odit/lfk-client-js";
import { createEventDispatcher } from "svelte";
import toast from "svelte-french-toast";
export let payment_modal_open = false;
export let original_data = {};
export let paid_amount_input = 0;
const dispatch = createEventDispatcher();
$: processed_last_submit = true;
$: createbtnenabled =
is_paid_amount_valid &&
!(paid_amount_input * 100 == original_data.paidAmount);
$: is_paid_amount_valid = paid_amount_input > 0;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
payment_modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("updating-donation"));
const editable = Object.assign({}, original_data);
editable.donor = editable.donor.id;
editable.paidAmount = Math.round(paid_amount_input * 100);
if (editable.responseType == "DISTANCEDONATION" || editable.runner) {
editable.runner = editable.runner.id;
DonationService.donationControllerPutDistance(
original_data.id,
editable
)
.then((result) => {
payment_modal_open = false;
//
toast.dismiss();
toast.success($_("donation-updated"));
dispatch("created", { donation: result });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
} else {
DonationService.donationControllerPutFixed(original_data.id, editable)
.then((result) => {
payment_modal_open = false;
//
toast.dismiss();
toast.success($_("donation-updated"));
dispatch("created", { donation: result });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
}
}
}
</script>
{#if payment_modal_open}
<div
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 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"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
/></svg
>
</div>
<div class="mt-3 text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("enter-payment")}
</h3>
<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-2 lg:gap-6">
<div class="w-full">
<label
for="token"
class="block text-sm font-medium text-gray-700"
>{$_("paid-amount")}</label
>
<div
class="inline-flex border-gray-300 border rounded-l-md rounded-r-md bg-gray-50 text-gray-500 w-full"
>
<input
autocomplete="off"
class:border-red-500={!is_paid_amount_valid}
class:focus:border-red-500={!is_paid_amount_valid}
class:focus:ring-red-500={!is_paid_amount_valid}
bind:value={paid_amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm p-2"
placeholder="2.00"
/>
<button
on:click={() => {
paid_amount_input = paid_amount_input = (
original_data.amount / 100
).toFixed(2);
}}
class="inline-flex items-center p-r-2 text-indigo-300 hover:text-indigo-700 text-sm"
>MAX</button
>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
>€</span
>
</div>
{#if !is_paid_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("payment-amount-must-be-greater-than-0-00eur")}
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 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"
>
{$_("save-changes")}
</button>
<button
on:click={() => {
payment_modal_open = false;
}}
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>
</div>
</div>
</div>
{/if}
@@ -1,117 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_donation = {
id: 0,
runner: {
firstname: "",
lastname: "",
},
donor: {
firstname: "",
lastname: "",
},
};
const dispatch = createEventDispatcher();
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
async function submit() {
dispatch("delete", { id: delete_donation.id });
modal_open = false;
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-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
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</div>
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("please-confirm-the-deletion-of-donation")}
</h3>
<div class="w-full">
<span class="inline-block"
><b>{$_("donor")}</b>: {delete_donation.donor.firstname}
{delete_donation.donor.lastname}</span
>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 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-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>
<button
on:click={() => {
modal_open = false;
}}
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>
</div>
</div>
</div>
{/if}
+277 -343
View File
@@ -1,352 +1,286 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import {
DonationService,
DonorService,
RunnerService,
} from "@odit/lfk-client-js";
import toast from "svelte-french-toast";
import { _ } from "svelte-i18n";
import store from "../../store";
import {
DonationService,
DonorService,
RunnerService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select";
let data_loaded = false;
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: donor = {};
$: runner = {};
$: current_donors = [];
$: current_runners = [];
$: amount_input = 0;
$: is_amount_valid = amount_input > 0;
$: is_everything_set =
editable.donor != null &&
((original_data.responseType == "DISTANCEDONATION" &&
editable?.runner != null) ||
original_data.responseType !== "DISTANCEDONATION");
$: changes_performed =
!(JSON.stringify(original_data) === JSON.stringify(editable)) ||
(original_data.responseType == "DISTANCEDONATION" &&
!(Math.floor(amount_input * 100) === original_data.amountPerDistance)) ||
(original_data.responseType !== "DISTANCEDONATION" &&
!(Math.floor(amount_input * 100) === original_data.amount));
$: save_enabled = changes_performed && is_amount_valid && 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(original_data, data);
editable = Object.assign(editable, original_data);
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.success($_("donation-deleted"));
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_donor = original_data;
});
}
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: "Donation is being updated",
duration: 2500,
}).showToast();
let postdata = {};
if (original_data.responseType === "DISTANCEDONATION") {
editable.amountPerDistance = Math.floor(amount_input * 100);
postdata = Object.assign(postdata, editable);
postdata.runner = postdata.runner.id;
postdata.donor = postdata.donor.id;
DonationService.donationControllerPutDistance(
original_data.id,
postdata
)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
Toastify({
text: "updated donation",
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.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;
Toastify({
text: "updated donation",
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
}
} else {
}
}
function deleteDonation() {
DonationService.donationControllerRemove(original_data.id, false)
.then((resp) => {
Toastify({
text: "Donation delete",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
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="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>
<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>
</div>
<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)}
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>
</section>
{:catch error}
<PromiseError {error} />
<PromiseError {error} />
{/await}
@@ -1,18 +0,0 @@
<script>
import { _ } from "svelte-i18n";
export let donor;
</script>
{#if !donor || donor.firstname == 0}
{$_("donor-has-no-associated-donations")}
{:else}
<div class="flex items-center">
<a
href="../donors/{donor.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 border border-current"
>{donor.firstname}
{#if donor.middlename}{donor.middlename}{/if}
{donor.lastname}</a
>
</div>
{/if}
@@ -1,18 +0,0 @@
<script>
import { _ } from "svelte-i18n";
export let runner;
</script>
{#if !runner || runner.firstname == 0}
{$_("fixed-donation")}
{:else}
<div class="text-sm font-medium text-gray-900">
<a
href="../runners/{runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 border border-current"
>{runner.firstname}
{#if runner.middlename}{runner.middlename}{/if}
{runner.lastname}</a
>
</div>
{/if}
@@ -1,16 +0,0 @@
<script>
import { _ } from "svelte-i18n";
export let status;
</script>
{#if status == "PAID"}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full 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}
@@ -1,21 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import TableActions from "../shared/TableActions.svelte";
export let detailsLink;
export let detailsAction;
export let deleteEnabled;
export let deleteAction;
export let paymentAction;
</script>
<button
on:click={paymentAction}
class="text-[#025a21] hover:text-green-900 mr-4">{$_("enter-payment")}</button
>
<TableActions
bind:detailsAction
bind:detailsLink
bind:deleteAction
bind:deleteEnabled
/>
+16 -23
View File
@@ -5,32 +5,25 @@
import DonationsOverview from "./DonationsOverview.svelte";
$: current_donations = [];
export let modal_open = false;
let addDonations;
</script>
<section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("donations")}
</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 />
<span 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>
<DonationsOverview bind:current_donations />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")}
<AddDonationModal
on:created={(event) => {
addDonations(event.detail.donations);
}}
bind:modal_open
/>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:CREATE')}
<AddDonationModal bind:current_donations bind:modal_open />
{/if}
@@ -5,8 +5,8 @@
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<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>
<img class="m-auto" style="height:15rem" src={donations_empty} alt="" />
<span class="font-bold">{$_('there-are-no-donations-yet')}</span><br />
<span>{$_('add-your-fist-donation')}</span>
</p>
</div>
+178 -275
View File
@@ -1,291 +1,194 @@
<script>
import { _ } from "svelte-i18n";
import { DonationService } from "@odit/lfk-client-js";
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { DonationService, DonorService } from "@odit/lfk-client-js";
import store from "../../store";
import Toastify from "toastify-js";
import DonationsEmptyState from "./DonationsEmptyState.svelte";
import AddDonationPaymentModal from "./AddDonationPaymentModal.svelte";
import { onMount } from "svelte";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { writable } from "svelte/store";
import TableBottom from "../shared/TableBottom.svelte";
import InputElement from "../shared/InputElement.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import DonationDonor from "./DonationDonor.svelte";
import DonationRunner from "./DonationRunner.svelte";
import DonationStatus from "./DonationStatus.svelte";
import DonationTableAction from "./DonationTableAction.svelte";
import DeleteDonationModal from "./DeleteDonationModal.svelte";
import {
donationDonorFilter,
donationRunnerFilter,
} from "../shared/tablefilters";
import toast from "svelte-french-toast";
$: searchvalue = "";
$: active_deletes = [];
$: active_edits = [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: dataLoaded = false;
export let current_donations = [];
export const addDonations = (donations) => {
current_donations = current_donations.concat(...donations);
options.update((options) => ({
...options,
data: current_donations,
}));
};
//Section table
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "donor",
header: () => $_("donor"),
cell: (info) => {
return renderComponent(DonationDonor, { donor: info.getValue() });
},
filterFn: `donor`,
},
{
accessorKey: "runner",
header: () => $_("runner"),
cell: (info) => {
return renderComponent(DonationRunner, { runner: info.getValue() });
},
filterFn: `runner`,
},
{
accessorKey: "amountPerDistance",
header: () => $_("amount-per-kilometer"),
cell: (info) => {
if (!info.getValue()) {
return $_("fixed-donation");
}
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })} €`;
},
enableColumnFilter: false,
},
{
accessorKey: "amount",
header: () => $_("donation-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })} €`;
},
enableColumnFilter: false,
},
{
accessorKey: "paidAmount",
header: () => $_("total-paid-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })} €`;
},
enableColumnFilter: false,
},
{
accessorKey: "status",
header: () => $_("status"),
cell: (info) => {
return renderComponent(DonationStatus, { status: info.getValue() });
},
enableColumnFilter: false,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(DonationTableAction, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_deletes = current_donations.filter(
(r) => r.id == info.row.original.id
);
},
paymentAction: () => {
active_edits = current_donations.filter(
(r) => r.id == info.row.original.id
);
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes(
"DONATION:DELETE"
),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
filterFns: {
donor: donationDonorFilter,
runner: donationRunnerFilter,
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
async function deleteDonation(delete_donation_id) {
await DonationService.donationControllerRemove(delete_donation_id, true);
current_donations = current_donations.filter(
(r) => r.id !== delete_donation_id
);
options.update((options) => ({
...options,
data: current_donations,
}));
toast.success($_("donation-deleted"));
}
onMount(async () => {
let page = 0;
let pagesize = 300;
while (page >= 0) {
const donations = await DonationService.donationControllerGetAll(
page,
pagesize
);
if (donations.length == 0) {
page = -2;
}
current_donations = current_donations.concat(...donations);
options.update((options) => ({
...options,
data: current_donations,
}));
dataLoaded = true;
page++;
const donations_promise = DonationService.donationControllerGetAll().then(
(val) => {
current_donations = val;
}
});
);
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
}
</script>
<AddDonationPaymentModal
original_data={active_edits[0]}
payment_modal_open={active_edits.length > 0}
paid_amount_input={(active_edits[0]?.paidAmount || 0) / 100}
on:created={(event) => {
current_donations = current_donations.map((d)=>{
if(d.id === event.detail.donation.id){
d.paidAmount = event.detail.donation.paidAmount;
}
return d;
})
options.update((options) => ({
...options,
data: current_donations,
}));
}}
/>
<DeleteDonationModal
delete_donation={active_deletes[0]}
modal_open={active_deletes.length > 0}
on:delete={(event) => {
deleteDonation(event.detail.id);
}}
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:GET")}
{#if !dataLoaded}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
{#await donations_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">{$_("donations-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
role="alert">
<p class="font-bold">donations are being loaded</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:else if current_donations.length === 0}
<DonationsEmptyState />
{: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="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()}
/>
{:then}
{#if current_donations.length === 0}
<DonationsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('donor')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('runner')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('amount-per-kilometer')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('donation-amount')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</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>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_donations as donation}
{#if donation.donor.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donation.donor.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donation.runner?.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donation.runner?.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(donation.id)}
<tr data-rowid="donation_{donation.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<a
href="../donors/{donation.donor.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{donation.donor.firstname}
{donation.donor.middlename || ''}
{donation.donor.lastname}</a>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donation.runner}
<div class="text-sm font-medium text-gray-900">
<a
href="../runners/{donation.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{donation.runner.firstname}
{donation.runner.middlename || ''}
{donation.runner.lastname}</a>
</div>
{:else}
<div class="text-sm font-medium text-gray-900">
{$_('fixed-donation')}
</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donation.amountPerDistance}
<div class="text-sm font-medium text-gray-900">
{(donation.amountPerDistance / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}€
</div>
{:else}
<div class="text-sm font-medium text-gray-900">
{$_('fixed-donation')}
</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
{(donation.amount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}€
</div>
</td>
{#if active_deletes[donation.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[donation.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
DonationService.donationControllerRemove(donation.id, false).then(
(resp) => {
current_donations = current_donations.filter(
(obj) => obj.id !== donation.id
);
Toastify({
text: 'Donation deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{donation.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:DELETE')}
<button
on:click={() => {
active_deletes[donation.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
<div class="h-2" />
<TableBottom {table} {selected} />
{/if}
{/await}
{/if}
<style>
table tbody tr td:nth-child(2) {
font-family: monospace;
}
</style>
+86 -126
View File
@@ -1,14 +1,15 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { DonorService } from "@odit/lfk-client-js";
import { focusTrap } from "svelte-focus-trap";
import {
DonorService
} from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail";
import isMobilePhone from "validator/es/lib/isMobilePhone";
import { createEventDispatcher } from "svelte";
import toast from "svelte-french-toast";
import Toastify from "toastify-js";
export let modal_open;
export let current_donors;
let firstname_input;
let lastname_input;
let middlename_input;
@@ -18,7 +19,6 @@
let address_input2;
let address_zipcode;
let address_city;
const dispatch = createEventDispatcher();
function focus(el) {
el.focus();
}
@@ -74,7 +74,10 @@
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("donor-is-being-added"));
const toast = Toastify({
text: $_('donor-is-being-added'),
duration: -1,
}).showToast();
let address = {};
if (address_checked === true) {
address = {
@@ -89,7 +92,7 @@
firstname: firstname_input_value,
lastname: lastname_input_value,
address,
receiptNeeded: address_checked,
receiptNeeded: address_checked
};
if (middlename_input_value) {
postdata.middlename = middlename_input_value;
@@ -108,15 +111,21 @@
email_input_value = "";
modal_open = false;
//
toast.dismiss();
toast.success($_("donor-added"));
dispatch("created", { donors: [result] });
Toastify({
text: $_('donor-added'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donors.push(result);
current_donors = current_donors;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
@@ -124,71 +133,59 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
}}>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
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 rounded-t-xl">
<div class="">
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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"
/></svg
>
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg>
</div>
<div class="mt-3">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-donor")}
{$_('create-a-new-donor')}
</h3>
<div class="mb-6">
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-nessecary-information-to-add-a-new-donor"
)}
{$_('please-provide-the-nessecary-information-to-add-a-new-donor')}
</p>
</div>
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700"
>{$_("first-name")}</label
>
class="block text-sm font-medium text-gray-700">{$_('first-name')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_("first-name")}
placeholder={$_('first-name')}
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
@@ -196,41 +193,34 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("first-name-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700"
>{$_("middle-name")}</label
>
class="block text-sm font-medium text-gray-700">{$_('middle-name')}</label>
<input
autocomplete="off"
placeholder={$_("middle-name")}
placeholder={$_('middle-name')}
bind:value={middlename_input_value}
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="lastname"
class="block text-sm font-medium text-gray-700"
>{$_("last-name")}</label
>
class="block text-sm font-medium text-gray-700">{$_('last-name')}</label>
<input
autocomplete="off"
placeholder={$_("last-name")}
placeholder="{$_('last-name')}"
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
@@ -238,25 +228,21 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("last-name-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="phone"
class="block text-sm font-medium text-gray-700"
>{$_("phone")}</label
>
class="block text-sm font-medium text-gray-700">{$_('phone')}</label>
<input
autocomplete="off"
placeholder={$_("phone")}
placeholder={$_('phone')}
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
@@ -264,27 +250,21 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{@html $_(
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number"
)}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="email"
class="block text-sm font-medium text-gray-700"
>{$_("e-mail-adress")}</label
>
class="block text-sm font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input
autocomplete="off"
placeholder={$_("e-mail-adress")}
placeholder={$_('e-mail-adress')}
class:border-red-500={!isEmailValidOrEmpty}
class:focus:border-red-500={!isEmailValidOrEmpty}
class:focus:ring-red-500={!isEmailValidOrEmpty}
@@ -292,13 +272,11 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-email-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-email-is-required')}
</span>
{/if}
</div>
@@ -309,22 +287,19 @@
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-semibold text-gray-700"
>{$_("receipt-needed")}</label
>
<label
for="comments"
class="font-medium text-gray-700">{$_('receipt-needed')}</label>
</div>
</div>
{#if address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700"
>{$_("address")}</label
>
class="block text-sm font-medium text-gray-700">{$_('address')}</label>
<input
autocomplete="off"
placeholder="Address"
@@ -335,41 +310,34 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("address-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('address-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700"
>{$_("apartment-suite-etc")}</label
>
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label>
<input
autocomplete="off"
placeholder={$_("apartment-suite-etc")}
placeholder={$_('apartment-suite-etc')}
bind:value={address_input2_value}
bind:this={address_input2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700"
>{$_("zip-postal-code")}</label
>
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label>
<input
autocomplete="off"
placeholder={$_("zip-postal-code")}
placeholder={$_('zip-postal-code')}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
@@ -377,22 +345,18 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-zipcode-postal-code-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-zipcode-postal-code-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700"
>City</label
>
class="block text-sm font-medium text-gray-700">City</label>
<input
autocomplete="off"
placeholder="City"
@@ -403,13 +367,11 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-city-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-city-is-required')}
</span>
{/if}
</div>
@@ -418,24 +380,22 @@
</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">
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create")}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="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")}
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
@@ -1,90 +1,92 @@
<script>
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 });
}
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { DonorService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let modal_open;
export let delete_donor;
const dispatch = createEventDispatcher();
function cancelDelete() {
modal_open = false;
dispatch("cancelDelete", { id: delete_donor.id });
}
function deleteDonor() {
DonorService.donorControllerRemove(
delete_donor.id,
true
)
.then((resp) => {
Toastify({
text: "Donor deleted",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {});
}
</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"
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>
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-blue-600" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M9.33 11.5h2.17A4.5 4.5 0 0116 16H9v1h8v-1a5.58 5.58 0 00-.89-3H19a5 5 0 014.52 2.85A13.15 13.15 0 0113 21c-2.76 0-5.1-.59-7-1.63v-9.3a6.97 6.97 0 013.33 1.43zM5 19a1 1 0 01-1 1H2a1 1 0 01-1-1v-9a1 1 0 011-1h2a1 1 0 011 1v9zM18 5a3 3 0 110 6 3 3 0 010-6zm-7-3a3 3 0 110 6 3 3 0 010-6z"/></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('attention')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
'do-you-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>
{/if}
-14
View File
@@ -1,14 +0,0 @@
<script>
import { _ } from "svelte-i18n";
export let address;
</script>
{#if !address || !address.address1}
{$_("no-address")}
{:else}
{address.address1}<br />
<!-- {address.address2 || ''}<br /> -->
{address.postalcode}
{address.city}
{address.country}
{/if}
+400 -407
View File
@@ -1,413 +1,406 @@
<script>
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);
});
}
import { _ } from "svelte-i18n";
import store from "../../store";
import { DonorService, DonationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail";
import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte";
let data_loaded = false;
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: current_donations = [];
$: 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 donation_promise = DonationService.donationControllerGetAll().then(
(val) => {
current_donations = val;
}
);
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) {
Toastify({
text: $_("donor-is-being-updated"),
duration: 2500,
}).showToast();
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;
editable.receiptNeeded = editable.address_checked;
DonorService.donorControllerPut(original_data.id, editable)
.then((resp) => {
Object.assign(original_data, editable);
original_data = original_data;
Toastify({
text: $_("updated-donor"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
} else {
}
}
function deleteDonor() {
DonorService.donorControllerRemove(original_data.id, false)
.then((resp) => {
Toastify({
text: $_("donor-deleted"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_donor = original_data;
});
}
</script>
{#await promise}
{$_("loading-donor-details")}
<ConfirmDonorDeletion bind:modal_open bind:delete_donor />
{#await promise && donation_promise}
{$_('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">
<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>
<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>
<br />
<span class="font-medium text-gray-700">{$_('donations')}:</span>
{#if current_donations.filter((d) => d.donor.id == editable.id).length > 0}
{#each current_donations.filter((o) => o.donor.id == editable.id) as d}
{#if d.responseType === 'DISTANCEDONATION'}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname}
{d.runner.middlename}
{d.runner.lastname}</a>
{:else}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1">{$_('fixed-donation')}:
{(d.amount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}€</a>
{/if}
{/each}
{:else}{$_('donor-has-no-associated-donations')}{/if}
</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>
{:catch error}
<PromiseError {error} />
<PromiseError {error} />
{/await}
@@ -1,29 +0,0 @@
<script>
import { _ } from "svelte-i18n";
export let donations;
</script>
{#if !donations || donations.length == 0}
{$_("donor-has-no-associated-donations")}
{:else}
{#each donations as donation}
{#if donation.responseType === "DISTANCEDONATION"}
<a
href="../donations/{donation.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1"
>{donation.runner.firstname}
{donation.runner.middlename || ""}
{donation.runner.lastname}</a
>
{:else}
<a
href="../donations/{donation.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")}:
{(donation.amount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}€</a
>
{/if}
{/each}
{/if}
+16 -64
View File
@@ -5,73 +5,25 @@
import DonorsOverview from "./DonorsOverview.svelte";
$: current_donors = [];
export let modal_open = false;
let addDonors;
</script>
<section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("donors")}
</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 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 />
<span 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}
</span>
<DonorsOverview bind:current_donors />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:CREATE")}
<AddDonorModal
on:created={(event) => {
addDonors(event.detail.donors);
}}
bind:modal_open
/>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:CREATE')}
<AddDonorModal bind:current_donors bind:modal_open />
{/if}
@@ -6,7 +6,7 @@
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full" style="height:15rem" src={donors_empty} alt="" />
<span class="font-bold">{$_("there-are-no-donors-yet")}</span><br />
<span>{$_("add-your-first-donor")}</span>
<span class="font-bold">{$_('there-are-no-donors-yet')}</span><br />
<span>{$_('add-your-first-donor')}</span>
</p>
</div>
+182 -236
View File
@@ -1,262 +1,208 @@
<script>
import { _ } from "svelte-i18n";
import { DonorService } from "@odit/lfk-client-js";
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { DonationService, DonorService } from "@odit/lfk-client-js";
import store from "../../store";
import DonorsEmptyState from "./DonorsEmptyState.svelte";
import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte";
import TableBottom from "../shared/TableBottom.svelte";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { writable } from "svelte/store";
import { onMount } from "svelte";
import InputElement from "../shared/InputElement.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import TableActions from "../shared/TableActions.svelte";
import DonorAddress from "./DonorAddress.svelte";
import DonorDonations from "./DonorDonations.svelte";
import { filterAddress, filterName } from "../shared/tablefilters";
import toast from "svelte-french-toast";
import Toastify from "toastify-js";
$: searchvalue = "";
$: active_deletes = [];
$: selectedDonors =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: dataLoaded = false;
$: current_donations = [];
let modal_open = false;
let delete_donor = {};
export let current_donors = [];
export const addDonors = (donors) => {
current_donors = current_donors.concat(...donors);
options.update((options) => ({
...options,
data: current_donors,
}));
};
//Section table
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "name",
header: () => $_("name"),
cell: (info) => {
const d = info.row.original;
if (d.middlename) {
return `${d.firstname} ${d.middlename} ${d.lastname}`;
} else {
return `${d.firstname} ${d.lastname}`;
}
},
filterFn: `name`,
},
{
accessorKey: "address",
header: () => $_("contact-information"),
cell: (info) => {
return renderComponent(DonorAddress, { address: info.getValue() });
},
filterFn: `address`,
},
{
accessorKey: "donations",
header: () => $_("sponsorings"),
cell: (info) => {
return renderComponent(DonorDonations, { donations: info.getValue() });
},
enableColumnFilter: false,
},
{
accessorKey: "donationAmount",
header: () => $_("total-donation-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}€`;
},
enableColumnFilter: false,
},
{
accessorKey: "paidDonationAmount",
header: () => $_("total-paid-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}€`;
},
enableColumnFilter: false,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_deletes = current_donors.filter(
(r) => r.id == info.row.original.id
);
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes(
"DONOR:DELETE"
),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
filterFns: {
name: filterName,
address: filterAddress,
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
const donors_promise = DonorService.donorControllerGetAll().then((val) => {
current_donors = val;
});
const table = createSvelteTable(options);
const donation_promise = DonationService.donationControllerGetAll().then(
(val) => {
current_donations = val;
}
);
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
}
onMount(async () => {
let page = 0;
let pagesize = 300;
while (page >= 0) {
const donors = await DonorService.donorControllerGetAll(page, pagesize);
if (donors.length == 0) {
page = -2;
}
current_donors = current_donors.concat(...donors);
options.update((options) => ({
...options,
data: current_donors,
}));
dataLoaded = true;
page++;
}
});
</script>
<ConfirmDonorDeletion
on:cancelDelete={(event) => {
active_deletes = active_deletes.filter((a) => a.id !== event.detail.id);
modal_open = false;
active_deletes[event.detail.id] = false;
}}
on:delete={async (event) => {
toast.loading($_("deleting-donor"));
await DonorService.donorControllerRemove(event.detail.id, true);
toast.dismiss();
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) => ({
...options,
data: current_donors,
}));
}}
modal_open={active_deletes.length > 0}
delete_donor={active_deletes[0]}
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")}
{#if !dataLoaded}
bind:modal_open
bind:delete_donor />
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
{#await donors_promise && donation_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">{$_("donors-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
role="alert">
<p class="font-bold">{$_('donors-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:else if current_donors.length === 0}
<DonorsEmptyState />
{: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="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()}
/>
{:then}
{#if current_donors.length === 0}
<DonorsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('name')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('contact-information')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('donations')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('total-donation-amount')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</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>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_donors as donor}
{#if donor.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || donor.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(donor.id)}
<tr data-rowid="donor_{donor.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{donor.firstname}
{donor.middlename || ''}
{donor.lastname}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donor.email}
<div class="text-sm text-gray-500">{donor.email}</div>
{/if}
{#if donor.phone}
<div class="text-sm text-gray-500">{donor.phone}</div>
{/if}
{#if donor.address.address1 !== null}
{donor.address.address1}<br />
{donor.address.address2 || ''}<br />
{donor.address.postalcode}
{donor.address.city}
{donor.address.country}
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if current_donations.filter((d) => d.donor.id == donor.id).length > 0}
{#each current_donations.filter((o) => o.donor.id == donor.id) as d}
{#if d.responseType === 'DISTANCEDONATION'}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname}
{d.runner.middlename}
{d.runner.lastname}</a>
{:else}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1">{$_('fixed-donation')}:
{(d.amount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}€</a>
{/if}
{/each}
{:else}{$_('donor-has-no-associated-donations')}{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{(donor.donationAmount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}€
</td>
{#if active_deletes[donor.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[donor.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
DonorService.donorControllerRemove(donor.id, false)
.then((resp) => {
current_donors = current_donors.filter((obj) => obj.id !== donor.id);
Toastify({
text: 'Donor deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_donor = donor;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{donor.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:DELETE')}
<button
on:click={() => {
active_deletes[donor.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
<div class="h-2" />
<TableBottom {table} {selected} />
{/if}
{/await}
{/if}
<style>
table tbody tr td:nth-child(2) {
font-family: monospace;
}
</style>
+187 -190
View File
@@ -1,196 +1,193 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
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;
});
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
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;
});
</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 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>
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left 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>
{/if}
<!-- /// -->
<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>
<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>
<tr>
<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>
<td>{l.name}</td>
<td>
{l.license || '?'}<br /><span
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')}</span>
</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>
@@ -1,72 +0,0 @@
<script>
import QrCodeScanner from "./QrCodeScanner.svelte";
let state = "scan_runner";
let runnerID = undefined;
let cardInfo = "";
</script>
<div class="p-4">
<h3 class="text-3xl font-bold">Card Assignment for Mobile</h3>
{#if state === "done"}
<p>Assigned Card {cardInfo} ✅</p>
<p>(not really, needs to be implemented)</p>
{:else}
<!-- -->
{#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>{runnerID}</p>
<h3 class="text-xl font-bold">Scan Card (Code 128 Barcode)</h3>
{/if}
<QrCodeScanner
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/"
)
) {
runnerID = JSON.parse(
atob(
e.detail.decodedText
.replace("https://portal.lauf-fuer-kaya.de/profile/", "")
.split(".")[1]
)
).id;
state = "scan_card";
}
}
if (state === "scan_card") {
if (
!e.detail.decodedText.includes(
"https://portal.lauf-fuer-kaya.de/profile/"
)
) {
cardInfo = e.detail.decodedText;
state = "done";
}
}
}}
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";
runnerID = undefined;
cardInfo = "";
}}
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}
<!-- -->
{/if}
</div>
+7 -16
View File
@@ -20,32 +20,23 @@
class="underline"
href="https://odit.services"
rel="noopener,noreferrer"
target="_blank">ODIT.Services</a
>
target="_blank">ODIT.Services</a>
</p>
<p class="text-sm text-gray-500 mt-4">
<a
class="underline"
target="_blank"
rel="noopener, noreferrer"
href="https://git.odit.services/lfk/frontend/">LfK!Frontend</a
>@<a
href="https://git.odit.services/lfk/frontend/">LfK!Frontend</a>@<a
class="underline"
target="_blank"
rel="noopener, noreferrer"
href="https://git.odit.services/lfk/frontend/src/tag/{releaseinfo}"
>{releaseinfo}</a
>
href="https://git.odit.services/lfk/frontend/src/tag/{releaseinfo}">{releaseinfo}</a>
-
<a class="underline" href="https://docs.lauf-fuer-kaya.de" target="_blank">{$_('documentation')}</a>
-
<a
rel="noopener, noreferrer"
class="underline"
href="https://docs.lauf-fuer-kaya.de"
target="_blank">{$_("documentation")}</a
>
<a class="underline" href="/privacy">{$_('privacy')}</a>
-
<a class="underline" href="/privacy">{$_("privacy")}</a>
-
<a class="underline" href="/imprint">{$_("imprint")}</a>
<a class="underline" href="/imprint">{$_('imprint')}</a>
</p>
</footer>
+10 -12
View File
@@ -1,20 +1,20 @@
<script>
import { _, getLocaleFromNavigator } from "svelte-i18n";
import { parse } from "marked";
import marked from "marked";
import Footer from "./Footer.svelte";
// import * as css from "../base/simple.css";
import * as css from "../base/simple.css";
let html = "";
async function load() {
let md = await fetch("/imprint_" + getLocaleFromNavigator() + ".md");
let text = (await md.text()).toString();
if (text.includes("<meta")) {
md.ok = false;
if(text.includes("<meta")){
md.ok=false
}
if (!md.ok) {
md = await fetch("/imprint_en.md");
text = await md.text();
}
html = parse(text);
html = marked(text);
}
const promise = load();
</script>
@@ -22,9 +22,8 @@
<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"
>
{$_("imprint")}
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl">
{$_('imprint')}
</h1>
</div>
</div>
@@ -32,17 +31,16 @@
<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">
{#await promise}
<p class="text-center w-full">{$_("imprint-loading")}</p>
<p class="text-center w-full">{$_('imprint-loading')}</p>
{:then}
<div class="simplecontent">
{@html html}
</div>
{:catch error}
<div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"
>
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_("general_promise_error")}</b>
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
+5 -8
View File
@@ -4,22 +4,19 @@
<body class="antialiased font-sans">
<div class="flex min-h-screen">
<div class="w-full bg-white flex items-center justify-center">
<div class="w-full bg-white flex items-center justify-center ">
<div class="max-w-sm m-8">
<div class="text-black text-5xl md:text-15xl font-black">
{$_("404title")}
{$_('404title')}
</div>
<div class="w-16 h-1 bg-purple-light my-3 md:my-6" />
<p
class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal"
>
{$_("404message")}
class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal">
{$_('404message')}
</p>
<a
href="/"
class="bg-transparent text-grey-darkest font-bold uppercase tracking-wide py-3 px-6 border-2 border-grey-light hover:border-grey rounded-lg"
>{$_("goback")}</a
>
class="bg-transparent text-grey-darkest font-bold uppercase tracking-wide py-3 px-6 border-2 border-grey-light hover:border-grey rounded-lg">{$_('goback')}</a>
</div>
</div>
</div>
+10 -12
View File
@@ -1,20 +1,20 @@
<script>
import { _, getLocaleFromNavigator } from "svelte-i18n";
import { parse } from "marked";
import marked from "marked";
import Footer from "./Footer.svelte";
// import * as css from "../base/simple.css?inline";
import * as css from "../base/simple.css";
let html = "";
async function load() {
let md = await fetch("/privacy_" + getLocaleFromNavigator() + ".md");
let text = (await md.text()).toString();
if (text.includes("<meta")) {
md.ok = false;
if(text.includes("<meta")){
md.ok=false
}
if (!md.ok) {
md = await fetch("/privacy_en.md");
text = await md.text();
}
html = parse(text);
html = marked(text);
}
const promise = load();
</script>
@@ -22,9 +22,8 @@
<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"
>
{$_("privacy")}
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl">
{$_('privacy')}
</h1>
</div>
</div>
@@ -32,17 +31,16 @@
<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">
{#await promise}
<p class="text-center w-full">{$_("privacy-loading")}</p>
<p class="text-center w-full">{$_('privacy-loading')}</p>
{:then}
<div class="simplecontent">
{@html html}
</div>
{:catch error}
<div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"
>
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_("general_promise_error")}</b>
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
@@ -1,80 +0,0 @@
<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) {
dispatch("detect", { decodedText });
}
// usually better to ignore and keep scanning
function onScanFailure(message) {
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>
+19 -40
View File
@@ -4,33 +4,26 @@
<div class="md:flex flex-col md:flex-row h-screen w-full">
<div
class="flex flex-col w-full md:w-64 text-gray-700 bg-white dark-mode:text-gray-200 dark-mode:bg-gray-800 flex-shrink-0"
>
class="flex flex-col w-full md:w-64 text-gray-700 bg-white dark-mode:text-gray-200 dark-mode:bg-gray-800 flex-shrink-0">
<div
class="flex-shrink-0 px-8 py-4 flex flex-row items-center justify-between"
>
class="flex-shrink-0 px-8 py-4 flex flex-row items-center justify-between">
<a
href="/#/test"
class="text-lg font-semibold tracking-widest text-gray-900 uppercase rounded-lg dark-mode:text-white focus:outline-none focus:shadow-outline"
>Sidebar</a
>
class="text-lg font-semibold tracking-widest text-gray-900 uppercase rounded-lg dark-mode:text-white focus:outline-none focus:shadow-outline">Sidebar</a>
<button
class="rounded-lg md:hidden focus:outline-none focus:shadow-outline"
>
class="rounded-lg md:hidden focus:outline-none focus:shadow-outline">
<svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
{#if open}
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
clip-rule="evenodd" />
{/if}
{#if !open}
<path
fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z"
clip-rule="evenodd"
/>
clip-rule="evenodd" />
{/if}
</svg>
</button>
@@ -38,63 +31,49 @@
<nav
:class:block={open}
:class:hidden={!open}
class="flex-grow md:block px-4 pb-4 md:pb-0 md:overflow-y-auto"
>
class="flex-grow md:block px-4 pb-4 md:pb-0 md:overflow-y-auto">
<a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-gray-200 rounded-lg dark-mode:bg-gray-700 dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Blog</a
>
href="#">Blog</a>
<a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Portfolio</a
>
href="#">Portfolio</a>
<a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">About</a
>
href="#">About</a>
<a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Contact</a
>
href="#">Contact</a>
<div class="relative">
<button
on:click={() => {
open = !open;
}}
class="flex flex-row items-center w-full px-4 py-2 mt-2 text-sm font-semibold text-left bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:focus:bg-gray-600 dark-mode:hover:bg-gray-600 md:block hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
>
class="flex flex-row items-center w-full px-4 py-2 mt-2 text-sm font-semibold text-left bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:focus:bg-gray-600 dark-mode:hover:bg-gray-600 md:block hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline">
<span>Dropdown</span>
<svg
fill="currentColor"
viewBox="0 0 20 20"
class="inline w-4 h-4 mt-1 ml-1 transition-transform duration-200 transform md:-mt-1"
><path
class="inline w-4 h-4 mt-1 ml-1 transition-transform duration-200 transform md:-mt-1"><path
fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd"
/></svg
>
clip-rule="evenodd" /></svg>
</button>
<div
class:block={open}
class:hidden={!open}
class="absolute right-0 w-full mt-2 origin-top-right rounded-md shadow-lg"
>
class="absolute right-0 w-full mt-2 origin-top-right rounded-md shadow-lg">
<div
class="px-2 py-2 bg-white rounded-md shadow dark-mode:bg-gray-800"
>
class="px-2 py-2 bg-white rounded-md shadow dark-mode:bg-gray-800">
<a
class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Link #1</a
>
href="#">Link #1</a>
<a
class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Link #2</a
>
href="#">Link #2</a>
<a
class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Link #3</a
>
href="#">Link #3</a>
</div>
</div>
</div>
+44 -54
View File
@@ -1,8 +1,9 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import Toastify from "toastify-js";
import { UserGroupService } from "@odit/lfk-client-js";
import toast from "svelte-french-toast";
export let modal_open;
export let current_groups;
let description_input_value;
@@ -31,7 +32,10 @@
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("group-is-being-added"));
const toast = Toastify({
text: $_('group-is-being-added'),
duration: -1,
}).showToast();
let postdata = {
name: name_input_value,
description: description_input_value,
@@ -42,8 +46,11 @@
description_input_value = "";
modal_open = false;
//
toast.dismiss();
toast.success($_("group-added"));
Toastify({
text: $_('group-added'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_groups.push(result);
current_groups = current_groups;
})
@@ -52,6 +59,8 @@
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
@@ -59,124 +68,105 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
}}>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
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 rounded-t-xl">
<div class="">
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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"
/></svg
>
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
</div>
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-user-group")}
{$_('create-a-new-user-group')}
</h3>
<div class="mb-6">
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-required-information-for-creating-a-new-user-group"
)}
{$_('please-provide-the-required-information-for-creating-a-new-user-group')}
</p>
</div>
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700"
>{$_("name")}</label
>
class="block text-sm font-medium text-gray-700">{$_('name')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_("name")}
placeholder="{$_('name')}"
class:border-red-500={!isNameValid}
class:focus:border-red-500={!isNameValid}
class:focus:ring-red-500={!isNameValid}
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isNameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("name-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700"
>{$_("description-optional")}</label
>
class="block text-sm font-medium text-gray-700">{$_('description-optional')}</label>
<input
autocomplete="off"
placeholder={$_("something-about-the-group")}
placeholder="{$_('something-about-the-group')}"
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create")}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="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")}
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
+220 -227
View File
@@ -1,227 +1,220 @@
<script>
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) => {});
}
</script>
{#await promise}
{$_("loading-group-detail")}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center"></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} />
{/await}
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import {
UserGroupService
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
let data_loaded = false;
export let params;
const promise = UserGroupService.userGroupControllerGetOne(params.groupid);
const colors = [
"#f3558e",
"#17b978",
"#3498db",
"#3f3b3b",
"#775ada",
"#7ed6df_#000000",
"#000000",
"#21e6c1_#000000",
"#c0392b",
"#d35400",
"#7f8c8d",
"#6ab04c",
"#4834d4",
"#ff1f5a",
"#eac100",
];
let matched_colors = [];
$: delete_triggered = false;
$: search_permission = "";
$: original_data = {};
$: editable = {};
$: changes_performed = !(JSON.stringify(original_data) == JSON.stringify(editable));
$: isGroupnameValid = editable.name !== "";
$: save_enabled =
changes_performed && isGroupnameValid
promise.then((data) => {
let current_target = "";
let colorindex = -1;
data.permissions = data.permissions.sort();
data.permissions.forEach((p) => {
const target = p.split(":")[0];
if (current_target !== p.split(":")[0]) {
colorindex++;
current_target = p.split(":")[0];
}
let background = colors[colorindex];
let foreground = "#fff";
if (background.includes("_")) {
foreground = background.split("_")[1];
background = background.split("_")[0];
}
matched_colors[target] = [background, foreground];
});
data_loaded = true;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
});
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: $_('updateing-group'),
duration: 2500,
}).showToast();
UserGroupService.userGroupControllerPut(original_data.id, editable)
.then((resp) => {
Object.assign(original_data, editable);
original_data = editable;
Object.assign(original_data, editable);
Toastify({
text: $_('group-updated'),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
} else {
}
}
function deleteGroup() {
UserGroupService.userGroupControllerRemove(original_data.id, true)
.then((resp) => {
location.replace("./");
})
.catch((err) => {});
}
</script>
{#await promise}
{$_('loading-group-detail')}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg class="flex-shrink-0 w-5 h-5 mr-2" fill="currentColor" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"></path></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="../">{$_('groups')}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">{editable.name}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.name}
<span data-id="group_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:DELETE')}
{#if delete_triggered}
<button
on:click={deleteGroup}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-group')}</button>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{/if}
</span>
</div>
<!-- -->
<div class="text-sm w-full">
<label
for="title"
class="font-medium text-gray-700">{$_('name')}</label>
<input
autocomplete="off"
placeholder={$_('name')}
type="text"
bind:value={editable.name}
class:border-red-500={!isGroupnameValid}
class:focus:border-red-500={!isGroupnameValid}
class:focus:ring-red-500={!isGroupnameValid}
name="title"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isGroupnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('group-name-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label
for="firstname"
class="font-medium text-gray-700">{$_('description')}</label>
<input
autocomplete="off"
placeholder={$_('description')}
type="text"
bind:value={editable.description}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full mt-8">
<p class="font-medium mb-4">
{$_('permissions')}
<a
class="px-4 py-2 bg-gray-500 rounded-md text-white"
href="/groups/{params.groupid}/permissions/">{$_('edit-permissions')}</a>
</p>
<div class="w-full sm:my-px sm:px-px sm:w-1/2">
<input
autocomplete="off"
placeholder="{$_('search-for-permission')}"
type="text"
bind:value={search_permission}
class="mt-4 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" />
</div>
{#each original_data.permissions as p}
{#if p.toLowerCase().includes(search_permission.toLowerCase())}
<span
style="background:{matched_colors[p.split(':')[0]][0]};color:{matched_colors[p.split(':')[0]][1]};"
class="mt-1 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-indigo-100 rounded">{p}</span>
<!-- -->
{/if}
{/each}
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}
+56 -76
View File
@@ -3,10 +3,9 @@
import {
PermissionService,
CreatePermission,
UserGroupService,
UserGroupService,
} from "@odit/lfk-client-js";
import toast from 'svelte-french-toast'
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
export let params;
let [
@@ -21,14 +20,15 @@
$: save_enabled =
JSON.stringify(grantedPermissions) ===
JSON.stringify(grantedPermissions_initial);
const group_promise = UserGroupService.userGroupControllerGetOne(
params.groupid
);
const group_promise = UserGroupService.userGroupControllerGetOne(params.groupid);
group_promise.then((data) => {
original_data = Object.assign(original_data, data);
});
function submit() {
toast.loading($_("updating-permissions"));
Toastify({
text: $_('updating-permissions'),
duration: 2500,
}).showToast();
to_delete.forEach((d) => {
promises = promises.concat([
PermissionService.permissionControllerRemove(d, true),
@@ -50,7 +50,11 @@
);
});
grantedPermissions_initial = grantedPermissions;
toast.success($_("permissions-updated"));
Toastify({
text: $_("permissions-updated"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
});
}
Object.values(CreatePermission.target).forEach((t) => {
@@ -58,15 +62,13 @@
allpermissions = allpermissions.concat([{ target: t, action: a }]);
});
});
UserGroupService.userGroupControllerGetPermissions(params.groupid).then(
(val) => {
val.directlyGranted.forEach((p) => {
delete p.responseType;
grantedPermissions = grantedPermissions.concat([p]);
});
grantedPermissions_initial = grantedPermissions;
}
);
UserGroupService.userGroupControllerGetPermissions(params.groupid).then((val) => {
val.directlyGranted.forEach((p) => {
delete p.responseType;
grantedPermissions = grantedPermissions.concat([p]);
});
grantedPermissions_initial = grantedPermissions;
});
</script>
{#await group_promise}
@@ -84,15 +86,12 @@
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"
><path
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
>
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="../../">{$_("user-groups")}</a><svg
<a class="mr-2" href="../../">{$_('user-groups')}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
@@ -102,10 +101,12 @@
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2"><a href="../">{original_data.name}</a></span>
@@ -121,45 +122,45 @@
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
><line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" /></svg
>
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">{$_("permissions")}</span>
<span class="mr-2">{$_('permissions')}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-4 text-3xl font-extrabold">
<div>
<div class="mb-8 text-3xl font-extrabold">
{$_('permissions')}:
{original_data.name}
<span>
{#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:w-auto sm:text-sm mb-1 lg:mb-0"
>{$_("save-changes")}</button
>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{: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:w-auto sm:text-sm"
>{$_("applying-changes")}</button
>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-yellow-600 text-base font-medium text-white hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('applying-changes')}</button>
{/if}
</div>
</span>
</div>
<!-- -->
<div class="flex flex-wrap -mx-1 overflow-hidden">
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/2">
{$_("available-permissions")}
{$_('verfuegbare')}
</div>
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/2">
{$_("granted")}
{$_('granted')}
</div>
</div>
<!-- -->
@@ -167,14 +168,12 @@
{#if allpermissions.length > 0}
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/2">
<div
class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center"
>
class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center">
{#each allpermissions as p}
{#if !(grantedPermissions.filter((o) => p.target == o.target && p.action == o.action).length > 0)}
{#if !(grantedPermissions.filter((o)=>p.target == o.target && p.action == o.action).length > 0)}
<p
class="block w-full mt-1 text-sm bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
>
{p.target + ":" + p.action}
class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input">
{p.target + ':' + p.action}
<button
on:click={() => {
grantedPermissions = grantedPermissions.concat([p]);
@@ -191,9 +190,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:w-auto sm:text-sm"
>+</button
>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-200 text-base font-medium text-black hover:bg-green-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm">+</button>
</p>
{/if}
{/each}
@@ -201,39 +198,22 @@
</div>
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/2">
<div
class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center"
>
class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center">
{#each grantedPermissions as p}
<p
class="block w-full mt-1 text-sm bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
>
{p.target + ":" + p.action}
class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input">
{p.target + ':' + p.action}
<button
on:click={() => {
grantedPermissions = grantedPermissions.filter(
(o) =>
o.target + ":" + o.action !== p.target + ":" + p.action
);
if (
to_add.some(
(o) =>
o.target + ":" + o.action ===
p.target + ":" + p.action
)
) {
to_add = to_add.filter(
(o) =>
o.target + ":" + o.action !==
p.target + ":" + p.action
);
grantedPermissions = grantedPermissions.filter((o) => o.target + ':' + o.action !== p.target + ':' + p.action);
if (to_add.some((o) => o.target + ':' + o.action === p.target + ':' + p.action)) {
to_add = to_add.filter((o) => o.target + ':' + o.action !== p.target + ':' + p.action);
} else {
to_delete = to_delete.concat([p.id]);
}
}}
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:w-auto sm:text-sm"
>-</button
>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-300 text-base font-medium text-black hover:bg-red-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm">-</button>
</p>
{/each}
</div>
+14 -15
View File
@@ -8,23 +8,22 @@
</script>
<section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("user-groups")}
</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}
<span 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>
<UserGroupsOverview bind:current_groups />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:CREATE")}
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:CREATE')}
<AddGroupModal bind:current_groups bind:modal_open />
{/if}
@@ -6,7 +6,7 @@
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={groups_empty} alt="" />
<span class="font-bold">{$_("there-are-no-groups-yet")}.</span><br />
<span>{$_("add-your-first-group")}</span>
<span class="font-bold">{$_('there-are-no-groups-yet')}.</span><br />
<span>{$_('add-your-first-group')}</span>
</p>
</div>
</div>
+25 -47
View File
@@ -13,14 +13,13 @@
);
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:GET")}
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')}
{#await groups_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">{$_("groups-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
role="alert">
<p class="font-bold">{$_('groups-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_groups.length === 0}
@@ -29,30 +28,26 @@
<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"
/>
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
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">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("name")}
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"
>
{$_("description")}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('description')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_("action")}</span>
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
@@ -62,10 +57,7 @@
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr
class="odd:bg-white even:bg-gray-100"
data-rowid="user_{group.id}"
>
<tr data-rowid="user_{group.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
@@ -80,53 +72,39 @@
</td>
{#if active_deletes[group.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[group.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
UserGroupService.userGroupControllerRemove(
group.id,
true
)
UserGroupService.userGroupControllerRemove(group.id, true)
.then((resp) => {
current_groups = current_groups.filter(
(obj) => obj.id !== group.id
);
current_groups = current_groups.filter((obj) => obj.id !== group.id);
})
.catch((err) => {
// error deleting user
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{group.id}"
class="text-indigo-600 hover:text-indigo-900">Details</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:DELETE")}
class="text-indigo-600 hover:text-indigo-900">Details</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:DELETE')}
<button
on:click={() => {
active_deletes[group.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
@@ -140,7 +118,7 @@
{: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>
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
+127 -153
View File
@@ -1,10 +1,9 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import toast from "svelte-french-toast";
import Toastify from "toastify-js";
export let modal_open;
export let current_organizations;
let name_input_dom;
@@ -25,7 +24,7 @@
$: address_input2_value = "";
$: address_zipcode_value = "";
$: address_city_value = "";
$: address_checked = false;
$: address_checked = true;
let address_input1;
let address_input2;
@@ -49,7 +48,10 @@
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("organization-is-being-added"));
const toast = Toastify({
text: $_("organization-is-being-added"),
duration: -1,
}).showToast();
let address = {};
if (address_checked === true) {
address = {
@@ -68,13 +70,17 @@
.then((result) => {
name = "";
modal_open = false;
toast.dismiss();
toast.success($_("organization-added"));
Toastify({
text: $_("organization-added"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_organizations = current_organizations.concat([result]);
})
.catch((err) => {})
.finally(() => {
processed_last_submit = true;
toast.hideToast();
});
}
}
@@ -82,70 +88,58 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
}}>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw]"
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 rounded-t-xl">
<div class="">
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="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path
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
>
height="24"><path
d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg>
</div>
<div class="mt-3 sm:text-left">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-organization")}
{$_('create-a-new-organization')}
</h3>
<div class="mb-6">
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-required-information-to-add-a-new-organization"
)}
{$_('please-provide-the-required-information-to-add-a-new-organization')}
</p>
</div>
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700"
>{$_("name")}</label
>
class="block text-sm font-medium text-gray-700">{$_('name')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_("name")}
placeholder={$_('name')}
class:border-red-500={!isOrgnameValid}
class:focus:border-red-500={!isOrgnameValid}
class:focus:ring-red-500={!isOrgnameValid}
@@ -153,13 +147,11 @@
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-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isOrgnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("organization-name-is-required")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('organization-name-is-required')}
</span>
{/if}
</div>
@@ -170,133 +162,115 @@
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-semibold text-gray-700"
>{$_("address")}</label
>
<label
for="comments"
class="font-medium text-gray-700">{$_('address')}</label>
</div>
</div>
{#if address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700"
>{$_("address")}</label
>
<input
autocomplete="off"
placeholder={$_("address")}
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={address_input1_value}
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-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={address_input2_value}
bind:this={address_input2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-2">
<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={address_zipcode_value}
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-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-4">
<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={address_city_value}
bind:this={address_city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-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>
{#if 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={address_input1_value}
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" />
{#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={address_input2_value}
bind:this={address_input2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</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={address_zipcode_value}
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" />
{#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={address_city_value}
bind:this={address_city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-city-is-required')}
</span>
{/if}
</div>
{/if}
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create")}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="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")}
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
@@ -0,0 +1,102 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
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) => {
Toastify({
text: "Organization deleted",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {});
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
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}
@@ -1,104 +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.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}
+476 -421
View File
@@ -1,421 +1,476 @@
<script>
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
/>
<ConfirmOrgDeletionModal bind:modal_open bind:delete_org />
{#if data_loaded}
<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}
{/if}
<script>
import {
GroupContactService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import Toastify from "toastify-js";
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) => {
Toastify({
text: $_("organization-deleted"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_org = original_object;
});
}
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: $_("updating-organization"),
duration: 2500,
}).showToast();
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);
Toastify({
text: $_("updated-organization"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
} else {
}
}
async function copy() {
if(!editable.registrationKey){
Toastify({
text: $_('you-have-to-save-your-changes-to-generate-a-link'),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
return;
}
valueCopy = registrationLink;
await tick();
areaDom.focus();
areaDom.select();
try {
const successful = document.execCommand("copy");
if (!successful) {
throw new Error();
}
Toastify({
text: $_("copied-link-to-clipboard"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
copied = true;
} catch (err) {
Toastify({
text: $_("error-whyile-copying-to-clipboard"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
}
// we can notifi by event or storage about copy status
valueCopy = null;
}
export let import_modal_open = false;
</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 />
<ConfirmOrgDeletion bind:modal_open bind:delete_org />
{#if data_loaded}
<section class="container p-5">
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original_object.name}
<span data-id="org_actions_${editable.id}">
<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="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">{$_('selfservice-registration')}</label>
</div>
</div>
<div>
{#if editable.registrationEnabled}
<div class="text-sm w-full">
<div on:click={copy} class="inline-flex w-full">
<p
name="token"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2">
{#if editable.registrationKey}
{registrationLink}
{:else}
{$_('you-have-to-save-your-changes-to-generate-a-link')}
{/if}
</p>
<div
class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg>
</div>
</div>
{#if editable.registrationKey}
<p class="text-gray-500 text-xs">
{$_('click-to-copy-the-link-into-your-clipboard')}
</p>
{/if}
</div>
{/if}
<!-- -->
<div>
<div class="flex items-start mt-2">
<div class="flex items-center h-5">
<input
bind:checked={editable.address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label
for="comments"
class="font-medium text-gray-700">{$_('address')}</label>
</div>
</div>
</div>
{#if editable.address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700">{$_('address')}</label>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('address-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label>
<input
autocomplete="off"
placeholder={$_('apartment-suite-etc')}
bind:value={editable.address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label>
<input
autocomplete="off"
placeholder={$_('zip-postal-code')}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-zipcode-postal-code-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700">{$_('city')}</label>
<input
autocomplete="off"
placeholder={$_('city')}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-city-is-required')}
</span>
{/if}
</div>
{/if}
</div>
</div>
</section>
{:else}
{#await promise}
{$_('organization-detail-is-being-loaded')}
{:catch error}
<PromiseError />
{/await}
{/if}
+219
View File
@@ -0,0 +1,219 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
let modal_open = false;
let delete_org = {};
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import store from "../../store";
import OrgsEmptyState from "./OrgsEmptyState.svelte";
import Toastify from "toastify-js";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
$: searchvalue = "";
$: active_deletes = [];
$: sponsoring_contracts_show = current_organizations.some((r) => r.is_selected === true);
$: cards_show = current_organizations.some((r) => r.is_selected === true);
$: generate_orgs = current_organizations.some((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="gridjs-input gridjs-search-input mb-4" />
<div class="h-12">
<GenerateSponsoringContracts
bind:sponsoring_contracts_show
bind:generate_orgs />
<GenerateRunnerCards
bind:cards_show
bind:generate_orgs />
<GenerateRunnerCertificates
bind:certificates_show
bind:generate_orgs />
</div>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span
on:click={() => {
const newstate = !current_organizations.some((r) => r.is_selected === true);
current_organizations = current_organizations.map((r) => {
r.is_selected = newstate;
return r;
});
}}
class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)}
{$_('deselect-all')}
{:else}{$_('select-all')}{/if}
</span>
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('name')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('address')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('contact')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_organizations as o}
{#if Object.values(o)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr data-rowid="org_{o.id}">
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={o.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{o.name}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if o.address.address1 !== null}
{o.address.address1}<br />
{o.address.address2 || ''}<br />
{o.address.postalcode}
{o.address.city}
{o.address.country}
{/if}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if o.contact}
<a
href="../contacts/{o.contact.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname}
{o.contact.middlename || ''}
{o.contact.lastname}</a>
{:else}{$_('no-contact-specified')}{/if}
</div>
</div>
</div>
</td>
{#if active_deletes[o.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[o.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false)
.then((resp) => {
current_organizations = current_organizations.filter((obj) => obj.id !== o.id);
Toastify({
text: 'Organization deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_org = o;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{o.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')}
<button
on:click={() => {
active_deletes[o.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}
+44 -246
View File
@@ -1,253 +1,51 @@
<script>
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;
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;
</script>
<section class="container p-5">
<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}
<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 />
</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"
bind:import_modal_open
/>
{#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 />
{/if}
+3 -3
View File
@@ -9,9 +9,9 @@
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={org_empty} alt="" />
<span class="font-bold">{$_("there-are-no-organizations-added-yet")}</span
><br />
<span>{$_("add-your-first-organization")}</span>
<span
class="font-bold">{$_('there-are-no-organizations-added-yet')}</span><br />
<span>{$_('add-your-first-organization')}</span>
</p>
</div>
@@ -1,150 +0,0 @@
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,
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;
@@ -1,81 +0,0 @@
<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}
@@ -1,197 +1,344 @@
<script>
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" });
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 = [];
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 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"));
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);
}
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);
}
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 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}
<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}
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
RunnerCardService,
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let cards_show = false;
export let generate_cards = [];
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: cards_dropdown_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "cards:dropdown" &&
e.target.parentNode?.parentNode?.id != "cards:dropdown:menu"
) {
cards_dropdown_open = false;
}
});
function generateRunnerCards(locale) {
cards_dropdown_open = false;
if (generate_orgs.length > 0) {
generateOrgCards(locale);
} else if (generate_teams.length > 0) {
generateTeamCards(locale);
} else if (generate_runners.length > 0) {
generateRunnersCards(locale);
} else {
generateCards(locale);
}
}
function generateCards(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(generate_cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('runnercards')}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
console.error(err);
});
}
async function generateRunnersCards(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let cards = [];
for (let runner of generate_runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
if(generate_runners.length == 1){
a.download = `${$_('runnercards')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`;
}
else{
a.download = `Runnercards-${locale}.pdf`;
}
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
}
async function generateTeamCards(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
for (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let cards = [];
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('runnercards')}_${t.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_teams.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
async function generateOrgCards(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
let count = 0;
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
for (const o of generate_orgs) {
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.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}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('runnercards')}_${o.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
</script>
{#if cards_show}
<div id="cards:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
cards_dropdown_open = !cards_dropdown_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-runnercards')}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg>
</button>
</div>
{#if cards_dropdown_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="cards:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateRunnerCards('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateRunnerCards('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}
@@ -1,175 +1,277 @@
<script>
import { _ } from "svelte-i18n";
import {
DonationService,
RunnerTeamService,
RunnerOrganizationService,
} 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 = [];
function generateCertificates(locale) {
if (generate_orgs.length > 0) {
generateOrgCertificates(locale);
} else if (generate_teams.length > 0) {
generateTeamCertificates(locale);
} else {
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"));
const current_donations =
(await DonationService.donationControllerGetAll()) || [];
let certificateRunners = [];
for (let runner of generate_runners) {
runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
documentServer
.generateCertificates(certificateRunners, locale)
.then((blob) => {
let fileName = `${$_("certificates")}-${locale}.pdf`;
if (generate_runners.length == 1) {
fileName = `${$_("certificates")}_${
generate_runners[0].firstname
}_${generate_runners[0].lastname}-${locale}-${createId()}.pdf`;
}
download(blob, fileName);
})
.catch((err) => {});
}
async function generateTeamCertificates(locale) {
toast.loading($_("generating-pdfs"));
let count = 0;
const current_donations =
(await DonationService.donationControllerGetAll()) || [];
for (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
documentServer
.generateCertificates(certificateRunners, locale)
.then((blob) => {
count++;
download(
blob,
`${$_("certificates")}_${t.name}-${locale}-${createId()}.pdf`
);
})
.catch((err) => {});
}
}
async function generateOrgCertificates(locale) {
toast.loading($_("generating-pdfs"));
const current_donations =
(await DonationService.donationControllerGetAll()) || [];
let count = 0;
let count_orgs = 0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id,
true
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await documentServer
.generateCertificates(certificateRunners, locale)
.then((blob) => {
download(
blob,
`${$_("certificates")}_${o.name}-${locale}-${createId()}.pdf`
);
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await documentServer
.generateCertificates(certificateRunners, locale)
.then((blob) => {
download(
blob,
`${$_("certificates")}_${o.name}_${
t.name
}-${locale}-${createId()}.pdf`
);
if (
count === o.teams.length &&
count_orgs === generate_orgs.length
) {
toast.dismiss();
toast.success($_("pdfs-successfully-generated"));
}
})
.catch((err) => {});
}
}
}
</script>
{#if certificates_show}
<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}
<script>
import { _ } from "svelte-i18n";
import {
DonationService,
RunnerTeamService,
RunnerOrganizationService
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let certificates_show = false;
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: certificates_dropdown_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "certificates:dropdown" &&
e.target.parentNode?.parentNode?.id != "certificates:dropdown:menu"
) {
certificates_dropdown_open = false;
}
});
function generateCertificates(locale) {
certificates_dropdown_open = false;
if (generate_orgs.length > 0) {
generateOrgCertificates(locale);
} else if (generate_teams.length > 0) {
generateTeamCertificates(locale);
} else {
generateRunnerCertificates(locale);
}
}
async function generateRunnerCertificates(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
const current_donations = await DonationService.donationControllerGetAll();
let certificateRunners = [];
for (let runner of generate_runners) {
runner.distanceDonations = current_donations.find((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
fetch(
`${config.baseurl}/documents/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
if(generate_runners.length == 1){
a.download = `${$_('certificates')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`;
}
else{
a.download = `${$_('certificates')}-${locale}.pdf`;
}
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
}
async function generateTeamCertificates(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
const current_donations = await DonationService.donationControllerGetAll();
for (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations = current_donations.find((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
fetch(
`${config.baseurl}/documents/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('certificates')}_${t.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_teams.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
async function generateOrgCertificates(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
let count = 0;
const current_donations = await DonationService.donationControllerGetAll();
for (const o of generate_orgs) {
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations = current_donations.find((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
fetch(
`${config.baseurl}/documents/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('certificates')}_${o.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
</script>
{#if certificates_show}
<div id="certificates:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
certificates_dropdown_open = !certificates_dropdown_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-runner-certificates')}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg>
</button>
</div>
{#if certificates_dropdown_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="certificates:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateCertificates('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateCertificates('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}
@@ -1,163 +1,257 @@
<script>
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
);
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 = "";
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"));
}
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) => {});
}
}
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 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}
<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}
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let sponsoring_contracts_show = false;
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: sponsoring_contracts_download_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
function generateSponsoringContract(locale) {
sponsoring_contracts_download_open = false;
if (generate_orgs.length > 0) {
generateOrgContracts(locale);
} else if (generate_teams.length > 0) {
generateTeamContracts(locale);
} else {
generateRunnerContracts(locale);
}
}
async function generateTeamContracts(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
for (const t of generate_teams) {
count++;
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
fetch(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('sponsorings')}_${t.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_teams.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
async function generateOrgContracts(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
for (const o of generate_orgs) {
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id
);
fetch(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('sponsorings')}_${o.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
function generateRunnerContracts(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(generate_runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
if(generate_runners.length == 1){
a.download = `${$_('sponsorings')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`;
}
a.download = `${$_('sponsorings')}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
console.error(err);
});
}
</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"
id="sponsoring:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateSponsoringContract('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateSponsoringContract('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}
Loaded 100 of 173 files, more files were not shown because too many files have changed in this diff. Show more