Compare commits

..

2 Commits
1.4.4 ... main

Author SHA1 Message Date
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
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
163 changed files with 8907 additions and 17530 deletions

View File

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

View File

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

View File

@@ -1,2 +1 @@
public/env.sample.js public/env.sample.js
.pnpm-store

View File

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

6
.gitignore vendored
View File

@@ -1,6 +1,10 @@
node_modules node_modules
package-lock.json
yarn.lock
*.map *.map
public/env.js public/env.js
public/index.html public/index.html
/dist /dist
.pnpm-store .yarn
.pnp.js
.yarnrc.yml

View File

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

View File

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

View File

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

View File

@@ -2,593 +2,8 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.4.4](https://git.odit.services/lfk/frontend/compare/1.4.3...1.4.4)
- 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) #### [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) - 🚑 [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) #### [0.12.3](https://git.odit.services/lfk/frontend/compare/0.12.2...0.12.3)
@@ -1643,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) - init [`32357ec`](https://git.odit.services/lfk/frontend/commit/32357ece0a7195ea1135c9c3e4c6c84323f95b4d)
- tmp [`1b7173c`](https://git.odit.services/lfk/frontend/commit/1b7173cda9134ee8058a00bdc030defa80d46bfc) - 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) - 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) - 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) - 🚚 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) - new Dashboard [`7270ce9`](https://git.odit.services/lfk/frontend/commit/7270ce9d32869abd4f6ac65ab7c2c87363633cbe)

View File

@@ -1,16 +1,14 @@
FROM registry.odit.services/hub/library/node:20.0.0-alpine3.17 as build FROM node:15.5.1-alpine3.12
ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app WORKDIR /app
COPY package.json ./
COPY package.json pnpm-lock.yaml vite.config.js tailwind.config.js postcss.config.cjs index.html ./ RUN yarn
RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8 COPY package.json *.config.js index.html ./
RUN mkdir /pnpm && pnpm config set store-dir /pnpm && pnpm i
COPY src ./src COPY src ./src
COPY public ./public COPY public ./public
RUN pnpm build RUN yarn build
# final image # final image
FROM registry.odit.services/library/nginx-brotli:3.15 as final FROM alpine
COPY --from=build /app/dist /usr/share/nginx/html 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 COPY ./nginx.conf /etc/nginx/nginx.conf

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,63 +1,56 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "1.4.4", "version": "0.12.4",
"type": "module", "scripts": {
"scripts": { "i18n-order": "node order.js",
"i18n-order": "node order.js", "dev": "vite",
"dev": "vite", "build": "vite build",
"format": "prettier --write --plugin-search-dir=. .", "release": "release-it",
"build": "vite build", "licenses:export": "license-exporter --json -o public"
"release": "release-it", },
"licenses:export": "license-exporter --json -o public" "license": "CC-BY-NC-SA-4.0",
}, "devDependencies": {
"license": "CC-BY-NC-SA-4.0", "check-password-strength": "2.0.2",
"devDependencies": { "@odit/lfk-client-js": "0.10.1",
"@odit/license-exporter": "0.0.12", "@odit/license-exporter": "0.0.11",
"@sveltejs/vite-plugin-svelte": "2.1.1", "@sveltejs/vite-plugin-svelte": "1.0.0-next.6",
"auto-changelog": "2.4.0", "@types/html-minifier": "4.0.0",
"autoprefixer": "10.4.14", "auto-changelog": "2.2.1",
"postcss": "8.4.23", "autoprefixer": "10.2.5",
"prettier": "^2.8.8", "csvtojson": "2.0.10",
"prettier-plugin-svelte": "^2.10.0", "gridjs": "3.4.0",
"release-it": "15.10.1", "html-minifier": "4.0.0",
"svelte-select": "3.17.0", "localforage": "1.9.0",
"tailwindcss": "3.3.2", "marked": "2.0.1",
"vite": "4.3.3" "release-it": "14.5.1",
}, "svelte": "3.37.0",
"release-it": { "svelte-focus-trap": "1.2.0",
"git": { "svelte-i18n": "3.3.9",
"commit": true, "svelte-preprocess": "4.7.0",
"requireCleanWorkingDir": false, "svelte-select": "3.17.0",
"commitMessage": "🚀RELEASE v${version}", "tailwindcss": "2.1.1",
"push": true, "tinro": "0.6.1",
"tag": true, "toastify-js": "1.10.0",
"tagName": null, "validator": "13.5.2",
"tagAnnotation": "v${version}" "vite": "2.1.5",
}, "vite-plugin-windicss": "0.12.5",
"npm": { "xlsx": "0.16.9"
"publish": false },
}, "release-it": {
"hooks": { "git": {
"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" "commit": true,
} "requireCleanWorkingDir": false,
}, "commitMessage": "🚀RELEASE v${version}",
"dependencies": { "push": false,
"@odit/lfk-client-js": "1.1.1", "tag": true,
"@paralleldrive/cuid2": "^2.2.0", "tagName": null,
"@tanstack/svelte-table": "^8.8.6", "tagAnnotation": "v${version}"
"bwip-js": "^3.4.0", },
"check-password-strength": "2.0.7", "npm": {
"csvtojson": "2.0.10", "publish": false
"localforage": "1.10.0", },
"marked": "4.3.0", "hooks": {
"svelte": "3.58.0", "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"
"svelte-french-toast": "1.0.4-beta.0", }
"svelte-i18n": "3.6.0", }
"tinro": "0.6.12", }
"validator": "13.9.0",
"xlsx": "0.18.5"
},
"volta": {
"node": "20.0.0"
}
}

3972
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>

View File

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

View File

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

View File

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

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" />

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

@@ -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}

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

View File

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

View File

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

View File

@@ -1,320 +1,281 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { RunnerCardService } from "@odit/lfk-client-js"; import { RunnerCardService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import toast from "svelte-french-toast"; import Toastify from "toastify-js";
import CardsEmptyState from "./CardsEmptyState.svelte"; import CardsEmptyState from "./CardsEmptyState.svelte";
import CardDetailModal from "./CardDetailModal.svelte"; import CardDetailModal from "./CardDetailModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import InputElement from "../shared/InputElement.svelte"; export let edit_modal_open = false;
import { export let runner = {};
createSvelteTable, export let editable = {};
flexRender, export let original_data = {};
getCoreRowModel, export let current_cards = [];
getFilteredRowModel, $: filtered_cards = current_cards.filter(function (c) {
getPaginationRowModel, if (
getSortedRowModel, c.code.toLowerCase().includes(searchvalue_lowercase) ||
renderComponent, c.runner?.firstname.toLowerCase().includes(searchvalue_lowercase) ||
} from "@tanstack/svelte-table"; c.runner?.middlename.toLowerCase().includes(searchvalue_lowercase) ||
import { writable } from "svelte/store"; c.runner?.lastname.toLowerCase().includes(searchvalue_lowercase) ||
import TableBottom from "../shared/TableBottom.svelte"; should_display_based_on_id(c.id)
import TableActions from "../shared/TableActions.svelte"; ) {
import TableHeader from "../shared/TableHeader.svelte"; return true;
import CardStatus from "./CardStatus.svelte"; }
import CardRunner from "./CardRunner.svelte"; });
import { onMount } from "svelte"; $: searchvalue = "";
import { runnerFilter, statusFilter } from "../shared/tablefilters"; $: searchvalue_lowercase = searchvalue.toLowerCase();
import DeleteCardModal from "./DeleteCardModal.svelte"; $: active_deletes = [];
$: cards_show = current_cards.some((r) => r.is_selected === true);
export let edit_modal_open = false; $: generate_cards = current_cards.filter((r) => r.is_selected === true);
export let runner = {}; const cards_promise = RunnerCardService.runnerCardControllerGetAll().then(
export let editable = {}; (val) => {
export let original_data = {}; current_cards = val;
export let current_cards = []; }
export const addCards = (cards) => { );
current_cards = current_cards.concat(...cards); function should_display_based_on_id(id) {
options.update((options) => ({ if (searchvalue.toString().slice(-1) === "*") {
...options, return id.toString().startsWith(searchvalue.replace("*", ""));
data: current_cards, }
})); return id.toString() === searchvalue;
}; }
const getRunnerLabel = (option) =>
$: dataLoaded = false; option?.firstname + " " + (option?.middlename || "") + " " + (option?.lastname || "{$_('non-blanko')}");
$: selected = function open_edit_modal(card) {
$table?.getSelectedRowModel().rows.map((row) => row.index) || []; if(card.runner?.id){
$: selectedCards = runner = Object.assign(
$table?.getSelectedRowModel().rows.map((row) => row.original) || []; { runner },
$: active_delete = undefined; { label: getRunnerLabel(card.runner), value: card.runner }
$: cards_show = generate_cards.length > 0; );
$: generate_cards = []; card.runner = card.runner.id;
}
const columns = [ else{
{ card.runner=null;
accessorKey: "code", runner = null
header: () => $_("code"), }
filterFn: `includesString`, editable = Object.assign(editable, card);
}, original_data = Object.assign(original_data, card);
{ edit_modal_open = true;
accessorKey: "runner", }
header: () => $_("runner"), // -----------------
cell: (info) => { let scrollTop = 0;
return renderComponent(CardRunner, { runner: info.getValue() }); $: rendered = filtered_cards;
}, let innerHeight = 0;
filterFn: `runner`, let ele;
}, $: updateSlice(scrollTop);
{ $: innerHeight = `${filtered_cards.length * 25}px`;
accessorKey: "enabled", $: if (ele) updateSlice();
cell: (info) => { function updateSlice() {
return renderComponent(CardStatus, { enabled: info.getValue() }); const height = ele ? parseInt(ele.clientHeight) : 100;
}, const init = scrollTop / 25;
header: () => $_("status"), const end = Math.ceil((scrollTop + height) / 25);
filterFn: `status`, rendered = filtered_cards.slice(init, end + 15);
}, }
{ function updateScroll($event) {
accessorKey: "actions", scrollTop = $event.target.scrollTop;
header: () => $_("action"), }
cell: (info) => { </script>
return renderComponent(TableActions, {
detailsAction: () => { <style>
open_edit_modal( table tbody {
current_cards[ display: block;
current_cards.findIndex((r) => r.id == info.row.original.id) overflow-y: scroll;
] }
);
}, table thead, table tbody tr {
deleteAction: () => { display: table;
active_delete = width: 100%;
current_cards[ table-layout: fixed;
current_cards.findIndex((r) => r.id == info.row.original.id) }
]; </style>
},
deleteEnabled: {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:UPDATE')}
store.state.jwtinfo.userdetails.permissions.includes("CARD:DELETE"), <CardDetailModal
}); bind:current_cards
}, bind:edit_modal_open
enableColumnFilter: false, bind:runner
enableSorting: false, bind:editable
}, bind:original_data />
]; {/if}
const options = writable({ {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
data: [], {#await cards_promise}
columns: columns, <div
initialState: { class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
pagination: { role="alert">
pageSize: 50, <p class="font-bold">{$_('loading-cards')}</p>
}, <p class="text-sm">{$_('this-might-take-a-moment')}</p>
}, </div>
filterFns: { {:then}
runner: runnerFilter, {#if current_cards.length === 0}
status: statusFilter, <CardsEmptyState />
}, {:else}
enableRowSelection: true, <input
getCoreRowModel: getCoreRowModel(), type="search"
getFilteredRowModel: getFilteredRowModel(), bind:value={searchvalue}
getPaginationRowModel: getPaginationRowModel(), placeholder={$_('datatable.search')}
getSortedRowModel: getSortedRowModel(), aria-label={$_('datatable.search')}
}); class="gridjs-input gridjs-search-input mb-4" />
<div class="h-12">
const table = createSvelteTable(options); <GenerateRunnerCards
bind:cards_show
function open_edit_modal(card) { bind:generate_cards />
const getRunnerLabel = (option) => </div>
option.firstname + <div
" " + class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
(option.middlename || "") + <table class="divide-y divide-gray-200 w-full">
" " + <thead class="bg-gray-50">
option.lastname; <tr>
if (card.runner?.id) { <th
runner = Object.assign( scope="col"
{ runner }, class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{ label: getRunnerLabel(card.runner), value: card.runner } <span
); on:click={() => {
card.runner = card.runner.id; const newstate = !current_cards.some((r) => r.is_selected === true);
} else { current_cards = current_cards.map((r) => {
card.runner = null; r.is_selected = newstate;
runner = null; return r;
} });
editable = Object.assign(editable, card); }}
original_data = Object.assign(original_data, card); class="underline cursor-pointer select-none">{#if current_cards.some((r) => r.is_selected === true)}
edit_modal_open = true; {$_('deselect-all')}
} {:else}{$_('select-all')}{/if}
</span>
async function deleteCard(delete_card_id) { </th>
await RunnerCardService.runnerCardControllerRemove(delete_card_id, true); <th
current_cards = current_cards.filter((r) => r.id !== delete_card_id); scope="col"
options.update((options) => ({ class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
...options, {$_('code')}
data: current_cards, </th>
})); <th
toast.success($_("card-deleted")); scope="col"
} class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('runner')}
onMount(async () => { </th>
toast.loading($_("loading-cards")); <th
let page = 0; scope="col"
let pagesize = 100; class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
while (page >= 0) { {$_('status')}
const cards = await RunnerCardService.runnerCardControllerGetAll( </th>
page, <th scope="col" class="relative px-6 py-3">
pagesize <span class="sr-only">{$_('action')}</span>
); </th>
if (cards.length == 0) { </tr>
page = -2; </thead>
} <tbody class="divide-y divide-gray-200 virtual-wrapper"
on:scroll={updateScroll}
current_cards = current_cards.concat(...cards); style="height: 70vh; width:100%"
options.update((options) => ({ bind:this={ele}
...options, >
data: current_cards, {#each filtered_cards as card, index}
})); {#if card.code
.toLowerCase()
dataLoaded = true; .includes(
page++; searchvalue.toLowerCase()
pagesize += 100; ) || card.runner?.firstname
} .toLowerCase()
toast.dismiss(); .includes(
toast.success($_("all-cards-loaded")); searchvalue.toLowerCase()
}); ) || card.runner?.middlename
</script> .toLowerCase()
.includes(
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:UPDATE")} searchvalue.toLowerCase()
<CardDetailModal ) || card.runner?.lastname
bind:edit_modal_open .toLowerCase()
bind:runner .includes(
bind:editable searchvalue.toLowerCase()
bind:original_data ) || should_display_based_on_id(card.id)}
on:dataUpdated={(event) => { <tr data-rowid="card_{card.id}">
current_cards[ <td class="px-6 py-4 whitespace-nowrap">
current_cards.findIndex((c) => c.id === event.detail.card.id) <input
] = event.detail.card; bind:checked={card.is_selected}
current_cards = current_cards; type="checkbox"
options.update((options) => ({ class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
...options, </td>
data: current_cards, <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">
{/if} <div class="flex items-center">
{#if card.runner}
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} <a
<DeleteCardModal href="../runners/{card.runner.id}"
delete_card={active_delete} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{card.runner.firstname}
modal_open={active_delete != undefined} {card.runner.middlename || ''}
on:delete={(event) => { {card.runner.lastname}</a>
deleteCard(event.detail.id); {:else}{$_('non-blanko')}{/if}
}} </div>
/> </td>
{#if !dataLoaded} <td class="px-6 py-4 whitespace-nowrap">
<div <div class="flex items-center">
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" {#if card.enabled}
role="alert" <span
> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('enabled')}</span>
<p class="font-bold">{$_("loading-cards")}</p> {:else}
<p class="text-sm">{$_("this-might-take-a-moment")}</p> <span
</div> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('disabled')}</span>
{:else if current_cards.length === 0} {/if}
<CardsEmptyState /> </div>
{:else} </td>
<div class="h-12 mt-1">
{#if selected.length > 0} {#if active_deletes[card.id] === true}
<button <td
type="button" class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex" <button
id="options-menu" on:click={() => {
on:click={async () => { active_deletes[card.id] = false;
const prom = []; }}
for (const card of selectedCards) { tabindex="0"
prom.push( class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
await RunnerCardService.runnerCardControllerRemove( <button
card.id, on:click={() => {
true RunnerCardService.runnerCardControllerRemove(card.id, false).then(
) (resp) => {
); current_cards = current_cards.filter(
} (obj) => obj.id !== card.id
await Promise.all(prom); );
for (const card of selectedCards) { Toastify({
current_cards = current_cards.filter((r) => r.id !== card.id); text: $_('card-deleted'),
} duration: 500,
options.update((options) => ({ backgroundColor:
...options, 'linear-gradient(to right, #00b09b, #96c93d)',
data: current_cards, }).showToast();
})); }
$table.resetRowSelection(); );
}} }}
> tabindex="0"
{$_("delete-cards")} class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
<svg </td>
xmlns="http://www.w3.org/2000/svg" {:else}
fill="none" <td
viewBox="0 0 24 24" class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
stroke-width="1.5" <button
stroke="currentColor" on:click={() => {
class="w-5 h-5" open_edit_modal(card);
> }}
<path class="text-indigo-600 hover:text-indigo-900">{$_('details')}</button>
stroke-linecap="round" {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:DELETE')}
stroke-linejoin="round" <button
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" on:click={() => {
/> active_deletes[card.id] = true;
</svg> }}
</button> tabindex="0"
{/if} class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
<GenerateRunnerCards {/if}
cards_show={selected.length > 0} </td>
bind:generate_cards={selectedCards} {/if}
/> </tr>
</div> {/if}
<div class="overflow-x-auto"> {/each}
<table class="w-full"> </tbody>
<thead class="border-b border-gray-400"> </table>
{#each $table.getHeaderGroups() as headerGroup} </div>
<tr class="select-none"> {/if}
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px"> {:catch error}
<InputElement <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
type="checkbox" <span class="inline-block align-middle mr-8">
checked={$table.getIsAllRowsSelected()} <b class="capitalize">{$_('general_promise_error')}</b>
indeterminate={$table.getIsSomeRowsSelected()} {error}
on:change={() => $table.toggleAllRowsSelected()} </span>
/> </div>
</th> {/await}
{#each headerGroup.headers as header} {/if}
<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>

View File

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

View File

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

View File

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

View File

@@ -9,22 +9,21 @@
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <span class="mb-1 text-3xl font-extrabold leading-tight">
{$_("contacts")} {$_('contacts')}
{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:CREATE')}
<button <button
on:click={() => { on:click={() => {
modal_open = true; modal_open = true;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
> {$_('create-a-new-contact')}
{$_("create-a-new-contact")}
</button> </button>
{/if} {/if}
</span> </span>
<ContactsOverview bind:current_contacts /> <ContactsOverview bind:current_contacts />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:CREATE')}
<AddContactModal bind:current_contacts bind:modal_open /> <AddContactModal bind:current_contacts bind:modal_open />
{/if} {/if}

View File

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

View File

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

View File

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

View File

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

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-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-normal text-grey-500">
{title}
</div>
<div class="text-xl font-bold font-mono">{value}</div>
</div>
<slot />
</div>
</div>
</a>

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}

View File

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

View File

@@ -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 = 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: response });
})
.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: response });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
}
}
}
</script>
{#if payment_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
payment_modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("enter-payment")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"you-can-enter-the-donations-paid-amount-manually-or-use-the-max-button-to-use-the-donations-exact-amount"
)}
</p>
</div>
<div class="grid grid-cols gap-6">
<div class="w-full">
<label
for="token"
class="block text-sm font-medium text-gray-700"
>{$_("paid-amount")}</label
>
<div
class="inline-flex border-gray-300 border rounded-l-md rounded-r-md bg-gray-50 text-gray-500 w-full"
>
<input
autocomplete="off"
class:border-red-500={!is_paid_amount_valid}
class:focus:border-red-500={!is_paid_amount_valid}
class:focus:ring-red-500={!is_paid_amount_valid}
bind:value={paid_amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm p-2"
placeholder="2.00"
/>
<button
on:click={() => {
paid_amount_input = paid_amount_input = (
original_data.amount / 100
).toFixed(2);
}}
class="inline-flex items-center p-r-2 text-indigo-300 hover:text-indigo-700 text-sm"
>MAX</button
>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
></span
>
</div>
{#if !is_paid_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("payment-amount-must-be-greater-than-0-00eur")}
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("save-changes")}
</button>
<button
on:click={() => {
payment_modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

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

View File

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

View File

@@ -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"
>{donor.firstname}
{#if donor.middlename}{donor.middlename}{/if}
{donor.lastname}</a
>
</div>
{/if}

View File

@@ -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"
>{runner.firstname}
{#if runner.middlename}{runner.middlename}{/if}
{runner.lastname}</a
>
</div>
{/if}

View File

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

View File

@@ -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
/>

View File

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

View File

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

View File

@@ -1,275 +1,194 @@
<script> <script>
import { _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { DonationService } from "@odit/lfk-client-js"; import { DonationService, DonorService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import Toastify from "toastify-js";
import DonationsEmptyState from "./DonationsEmptyState.svelte"; 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 = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: active_edits = [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: dataLoaded = false;
export let current_donations = []; export let current_donations = [];
export const addDonations = (donations) => { const donations_promise = DonationService.donationControllerGetAll().then(
current_donations = current_donations.concat(...donations); (val) => {
options.update((options) => ({ current_donations = val;
...options, }
data: current_donations, );
})); function should_display_based_on_id(id) {
}; if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
//Section table }
const columns = [ return id.toString() === searchvalue;
{
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($_("donation-deleted"));
} }
onMount(async () => {
const donations = await DonationService.donationControllerGetAll();
current_donations = donations;
options.update((options) => ({
...options,
data: current_donations,
}));
dataLoaded = true;
});
</script> </script>
<AddDonationPaymentModal {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
original_data={active_edits[0]} {#await donations_promise}
payment_modal_open={active_edits.length > 0}
paid_amount_input={(active_edits[0]?.paidAmount || 0) / 100}
on:created={(event) => {
current_donations[
current_donations.findIndex((d) => d.id === event.detail.donation.id)
].paidAmount = event.detail.donation.paidAmount;
options.update((options) => ({
...options,
data: current_donations,
}));
}}
/>
<DeleteDonationModal
delete_donation={active_deletes[0]}
modal_open={active_deletes.length > 0}
on:delete={(event) => {
deleteDonation(event.detail.id);
}}
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:GET")}
{#if !dataLoaded}
<div <div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert" role="alert">
> <p class="font-bold">donations are being loaded</p>
<p class="font-bold">{$_("donations-are-being-loaded")}</p> <p class="text-sm">{$_('this-might-take-a-moment')}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:else if current_donations.length === 0} {:then}
<DonationsEmptyState /> {#if current_donations.length === 0}
{:else} <DonationsEmptyState />
<input {:else}
type="search" <input
bind:value={searchvalue} type="search"
placeholder={$_("datatable.search")} bind:value={searchvalue}
aria-label={$_("datatable.search")} placeholder={$_('datatable.search')}
class="mb-4" aria-label={$_('datatable.search')}
/> class="gridjs-input gridjs-search-input mb-4" />
<div <div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll" class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
> <table class="divide-y divide-gray-200 w-full">
<table class="w-full"> <thead class="bg-gray-50">
<thead class="border-b border-gray-400"> <tr>
{#each $table.getHeaderGroups() as headerGroup} <th
<tr class="select-none"> scope="col"
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<InputElement {$_('donor')}
type="checkbox" </th>
checked={$table.getIsAllRowsSelected()} <th
indeterminate={$table.getIsSomeRowsSelected()} scope="col"
on:change={() => $table.toggleAllRowsSelected()} 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> </th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr> </tr>
{/each} </thead>
</thead> <tbody class="divide-y divide-gray-200">
<tbody> {#each current_donations as donation}
{#each $table.getRowModel().rows as row} {#if donation.donor.firstname
<tr class="odd:bg-white even:bg-gray-100"> .toLowerCase()
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px"> .includes(
<InputElement searchvalue.toLowerCase()
type="checkbox" ) || donation.donor.lastname
checked={row.getIsSelected()} .toLowerCase()
on:change={() => row.toggleSelected()} .includes(
/> searchvalue.toLowerCase()
</td> ) || donation.runner?.firstname
{#each row.getVisibleCells() as cell} .toLowerCase()
<td> .includes(
<svelte:component searchvalue.toLowerCase()
this={flexRender( ) || donation.runner?.lastname
cell.column.columnDef.cell, .toLowerCase()
cell.getContext() .includes(
)} searchvalue.toLowerCase()
/> ) || should_display_based_on_id(donation.id)}
</td> <tr data-rowid="donation_{donation.id}">
{/each} <td class="px-6 py-4 whitespace-nowrap">
</tr> <div class="flex items-center">
{/each} <a
</tbody> href="../donors/{donation.donor.id}"
</table> 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>
<div class="h-2" /> {/await}
<TableBottom {table} {selected} />
{/if}
{/if} {/if}
<style>
table tbody tr td:nth-child(2) {
font-family: monospace;
}
</style>

View File

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

View File

@@ -1,9 +1,9 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { DonorService } from "@odit/lfk-client-js"; import { DonorService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
export let modal_open; export let modal_open;
export let delete_donor; export let delete_donor;
@@ -13,61 +13,60 @@
dispatch("cancelDelete", { id: delete_donor.id }); dispatch("cancelDelete", { id: delete_donor.id });
} }
function deleteDonor() { function deleteDonor() {
dispatch("delete", { id: delete_donor.id }); 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> </script>
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside use:clickOutside
on:click_outside={cancelDelete} on:click_outside={cancelDelete}>
>
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0" class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" data-id="modal_backdrop" />
/>
</div> </div>
<span <span
class="hidden sm:inline-block sm:align-middle sm:h-screen" class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span aria-hidden="true">&#8203;</span>
>
<div <div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline" aria-labelledby="modal-headline">
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10" class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
> <svg 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>
<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>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("attention")} {$_('attention')}
</h3> </h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_( {$_(
"do-you-want-to-delete-this-donor-with-all-related-donations" 'do-you-want-to-delete-this-donor-with-all-related-donations'
)} )}
<br /> <br />
{$_("all-associated-donations-will-get-deleted-as-well")} {$_('all-associated-donations-will-get-deleted-as-well')}
</p> </p>
</div> </div>
</div> </div>
@@ -77,16 +76,14 @@
<button <button
on:click={deleteDonor} on:click={deleteDonor}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
> {$_('confirm-delete-donor-with-all-donations')}
{$_("confirm-delete-donor-with-all-donations")}
</button> </button>
<button <button
on:click={cancelDelete} on:click={cancelDelete}
type="button" type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
> {$_('cancel-keep-donor')}
{$_("cancel-keep-donor")}
</button> </button>
</div> </div>
</div> </div>

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}

View File

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

View File

@@ -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 bg-green-700 text-white mr-1"
>{$_("fixed-donation")}:
{(donation.amount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</a
>
{/if}
{/each}
{/if}

View File

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

View File

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

View File

@@ -1,252 +1,208 @@
<script> <script>
import { _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { DonorService } from "@odit/lfk-client-js"; import { DonationService, DonorService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import DonorsEmptyState from "./DonorsEmptyState.svelte"; import DonorsEmptyState from "./DonorsEmptyState.svelte";
import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte"; import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte";
import TableBottom from "../shared/TableBottom.svelte"; import Toastify from "toastify-js";
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";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: selectedDonors = $: current_donations = [];
$table?.getSelectedRowModel().rows.map((row) => row.original) || []; let modal_open = false;
$: selected = let delete_donor = {};
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: dataLoaded = false;
export let current_donors = []; export let current_donors = [];
export const addDonors = (donors) => { const donors_promise = DonorService.donorControllerGetAll().then((val) => {
current_donors = current_donors.concat(...donors); current_donors = val;
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 table = createSvelteTable(options); const donation_promise = DonationService.donationControllerGetAll().then(
(val) => {
current_donations = val;
}
);
function should_display_based_on_id(id) { function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") { if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", "")); return id.toString().startsWith(searchvalue.replace("*", ""));
} }
return id.toString() === searchvalue; return id.toString() === searchvalue;
} }
onMount(async () => {
const donors = await DonorService.donorControllerGetAll();
current_donors = donors;
options.update((options) => ({
...options,
data: donors,
}));
dataLoaded = true;
});
</script> </script>
<ConfirmDonorDeletion <ConfirmDonorDeletion
on:cancelDelete={(event) => { 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) => { bind:modal_open
toast.loading($_("deleting-donor")); bind:delete_donor />
await DonorService.donorControllerRemove(event.detail.id, true); {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
toast.dismiss(); {#await donors_promise && donation_promise}
toast($_("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}
<div <div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert" role="alert">
> <p class="font-bold">{$_('donors-are-being-loaded')}</p>
<p class="font-bold">{$_("donors-are-being-loaded")}</p> <p class="text-sm">{$_('this-might-take-a-moment')}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:else if current_donors.length === 0} {:then}
<DonorsEmptyState /> {#if current_donors.length === 0}
{:else} <DonorsEmptyState />
<input {:else}
type="search" <input
bind:value={searchvalue} type="search"
placeholder={$_("datatable.search")} bind:value={searchvalue}
aria-label={$_("datatable.search")} placeholder={$_('datatable.search')}
class="mb-4" aria-label={$_('datatable.search')}
/> class="gridjs-input gridjs-search-input mb-4" />
<div <div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll" class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
> <table class="divide-y divide-gray-200 w-full">
<table class="w-full"> <thead class="bg-gray-50">
<thead class="border-b border-gray-400"> <tr>
{#each $table.getHeaderGroups() as headerGroup} <th
<tr class="select-none"> scope="col"
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<InputElement {$_('name')}
type="checkbox" </th>
checked={$table.getIsAllRowsSelected()} <th
indeterminate={$table.getIsSomeRowsSelected()} scope="col"
on:change={() => $table.toggleAllRowsSelected()} 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> </th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr> </tr>
{/each} </thead>
</thead> <tbody class="divide-y divide-gray-200">
<tbody> {#each current_donors as donor}
{#each $table.getRowModel().rows as row} {#if donor.firstname
<tr class="odd:bg-white even:bg-gray-100"> .toLowerCase()
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px"> .includes(
<InputElement searchvalue.toLowerCase()
type="checkbox" ) || donor.lastname
checked={row.getIsSelected()} .toLowerCase()
on:change={() => row.toggleSelected()} .includes(
/> searchvalue.toLowerCase()
</td> ) || should_display_based_on_id(donor.id)}
{#each row.getVisibleCells() as cell} <tr data-rowid="donor_{donor.id}">
<td> <td class="px-6 py-4 whitespace-nowrap">
<svelte:component <div class="flex items-center">
this={flexRender( <div class="ml-4">
cell.column.columnDef.cell, <div class="text-sm font-medium text-gray-900">
cell.getContext() {donor.firstname}
)} {donor.middlename || ''}
/> {donor.lastname}
</td> </div>
{/each} </div>
</tr> </div>
{/each} </td>
</tbody> <td class="px-6 py-4 whitespace-nowrap">
</table> {#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>
<div class="h-2" /> {/await}
<TableBottom {table} {selected} />
{/if}
{/if} {/if}
<style>
table tbody tr td:nth-child(2) {
font-family: monospace;
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,237 +1,220 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import store from "../../store"; import store from "../../store";
import { UserGroupService } from "@odit/lfk-client-js"; import {
UserGroupService
import PromiseError from "../base/PromiseError.svelte"; } from "@odit/lfk-client-js";
let data_loaded = false; import Toastify from "toastify-js";
export let params; import PromiseError from "../base/PromiseError.svelte";
const promise = UserGroupService.userGroupControllerGetOne(params.groupid); let data_loaded = false;
const colors = [ export let params;
"#f3558e", const promise = UserGroupService.userGroupControllerGetOne(params.groupid);
"#17b978", const colors = [
"#3498db", "#f3558e",
"#3f3b3b", "#17b978",
"#775ada", "#3498db",
"#7ed6df_#000000", "#3f3b3b",
"#000000", "#775ada",
"#21e6c1_#000000", "#7ed6df_#000000",
"#c0392b", "#000000",
"#d35400", "#21e6c1_#000000",
"#7f8c8d", "#c0392b",
"#6ab04c", "#d35400",
"#4834d4", "#7f8c8d",
"#ff1f5a", "#6ab04c",
"#eac100", "#4834d4",
]; "#ff1f5a",
let matched_colors = []; "#eac100",
$: delete_triggered = false; ];
$: search_permission = ""; let matched_colors = [];
$: original_data = {}; $: delete_triggered = false;
$: editable = {}; $: search_permission = "";
$: changes_performed = !( $: original_data = {};
JSON.stringify(original_data) == JSON.stringify(editable) $: editable = {};
); $: changes_performed = !(JSON.stringify(original_data) == JSON.stringify(editable));
$: isGroupnameValid = editable.name !== ""; $: isGroupnameValid = editable.name !== "";
$: save_enabled = changes_performed && isGroupnameValid; $: save_enabled =
promise.then((data) => { changes_performed && isGroupnameValid
let current_target = ""; promise.then((data) => {
let colorindex = -1; let current_target = "";
data.permissions = data.permissions.sort(); let colorindex = -1;
data.permissions.forEach((p) => { data.permissions = data.permissions.sort();
const target = p.split(":")[0]; data.permissions.forEach((p) => {
if (current_target !== p.split(":")[0]) { const target = p.split(":")[0];
colorindex++; if (current_target !== p.split(":")[0]) {
current_target = p.split(":")[0]; colorindex++;
} current_target = p.split(":")[0];
let background = colors[colorindex]; }
let foreground = "#fff"; let background = colors[colorindex];
if (background.includes("_")) { let foreground = "#fff";
foreground = background.split("_")[1]; if (background.includes("_")) {
background = background.split("_")[0]; foreground = background.split("_")[1];
} background = background.split("_")[0];
matched_colors[target] = [background, foreground]; }
}); matched_colors[target] = [background, foreground];
data_loaded = true; });
original_data = Object.assign(original_data, data); data_loaded = true;
editable = Object.assign(editable, original_data); original_data = Object.assign(original_data, data);
}); editable = Object.assign(editable, original_data);
function submit() { });
if (data_loaded === true && save_enabled) { function submit() {
toast($_("updating-group")); if (data_loaded === true && save_enabled) {
UserGroupService.userGroupControllerPut(original_data.id, editable) Toastify({
.then((resp) => { text: $_('updateing-group'),
Object.assign(original_data, editable); duration: 2500,
original_data = editable; }).showToast();
Object.assign(original_data, editable); UserGroupService.userGroupControllerPut(original_data.id, editable)
toast.success($_("group-updated")); .then((resp) => {
}) Object.assign(original_data, editable);
.catch((err) => {}); original_data = editable;
} else { Object.assign(original_data, editable);
} Toastify({
} text: $_('group-updated'),
function deleteGroup() { duration: 2500,
UserGroupService.userGroupControllerRemove(original_data.id, true) backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
.then((resp) => { }).showToast();
location.replace("./"); })
}) .catch((err) => {});
.catch((err) => {}); } else {
} }
</script> }
function deleteGroup() {
{#await promise} UserGroupService.userGroupControllerRemove(original_data.id, true)
{$_("loading-group-detail")} .then((resp) => {
{:then} location.replace("./");
<section class="container p-5 select-none"> })
<div class="flex flex-row mb-4"> .catch((err) => {});
<div class="w-full"> }
<nav class="w-full flex"> </script>
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center"> {#await promise}
<svg {$_('loading-group-detail')}
class="flex-shrink-0 w-5 h-5 mr-2" {:then}
fill="currentColor" <section class="container p-5 select-none">
width="24" <div class="flex flex-row mb-4">
height="24" <div class="w-full">
xmlns="http://www.w3.org/2000/svg" <nav class="w-full flex">
viewBox="0 0 640 512" <ol class="list-none flex flex-row items-center justify-start">
><path <li class="flex items-center">
fill="currentColor" <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>
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" </li>
/></svg <li class="flex items-center">
> <a class="mr-2" href="../">{$_('groups')}</a><svg
</li> stroke="currentColor"
<li class="flex items-center"> fill="none"
<a class="mr-2" href="../">{$_("groups")}</a><svg stroke-width="2"
stroke="currentColor" viewBox="0 0 24 24"
fill="none" stroke-linecap="round"
stroke-width="2" stroke-linejoin="round"
viewBox="0 0 24 24" class="h-3 w-3 mr-2 stroke-current"
stroke-linecap="round" height="1em"
stroke-linejoin="round" width="1em"
class="h-3 w-3 mr-2 stroke-current" xmlns="http://www.w3.org/2000/svg"><line
height="1em" x1="5"
width="1em" y1="12"
xmlns="http://www.w3.org/2000/svg" x2="19"
><line x1="5" y1="12" x2="19" y2="12" /> y2="12" />
<polyline points="12 5 19 12 12 19" /></svg <polyline points="12 5 19 12 12 19" /></svg>
> </li>
</li> <li class="flex items-center">
<li class="flex items-center"> <span class="mr-2">{editable.name}</span>
<span class="mr-2">{editable.name}</span> </li>
</li> </ol>
</ol> </nav>
</nav> </div>
</div> </div>
</div> <div class="mb-8 text-3xl font-extrabold leading-tight">
<div class="mb-8 text-3xl font-extrabold leading-tight"> {original_data.name}
{original_data.name} <span data-id="group_actions_${editable.id}">
<span data-id="group_actions_${editable.id}"> {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:DELETE')}
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:DELETE")} {#if delete_triggered}
{#if delete_triggered} <button
<button on:click={deleteGroup}
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>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm" <button
>{$_("confirm-deletion")}</button on:click={() => {
> delete_triggered = !delete_triggered;
<button }}
on:click={() => { class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
delete_triggered = !delete_triggered; {/if}
}} {#if !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" <button
>{$_("cancel")}</button on:click={() => {
> delete_triggered = true;
{/if} }}
{#if !delete_triggered} type="button"
<button class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-group')}</button>
on:click={() => { {/if}
delete_triggered = true; {/if}
}} {#if !delete_triggered}
type="button" <button
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm" disabled={!save_enabled}
>{$_("delete-group")}</button class:opacity-50={!save_enabled}
> type="button"
{/if} on:click={submit}
{/if} 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 !delete_triggered} {/if}
<button </span>
disabled={!save_enabled} </div>
class:opacity-50={!save_enabled} <!-- -->
type="button" <div class="text-sm w-full">
on:click={submit} <label
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm" for="title"
>{$_("save-changes")}</button class="font-medium text-gray-700">{$_('name')}</label>
> <input
{/if} autocomplete="off"
</span> placeholder={$_('name')}
</div> type="text"
<!-- --> bind:value={editable.name}
<div class="text-sm w-full"> class:border-red-500={!isGroupnameValid}
<label for="title" class="font-medium text-gray-700">{$_("name")}</label> class:focus:border-red-500={!isGroupnameValid}
<input class:focus:ring-red-500={!isGroupnameValid}
autocomplete="off" name="title"
placeholder={$_("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" />
type="text" {#if !isGroupnameValid}
bind:value={editable.name} <span
class:border-red-500={!isGroupnameValid} class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
class:focus:border-red-500={!isGroupnameValid} {$_('group-name-is-required')}
class:focus:ring-red-500={!isGroupnameValid} </span>
name="title" {/if}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" </div>
/> <div class="text-sm w-full">
{#if !isGroupnameValid} <label
<span for="firstname"
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1" class="font-medium text-gray-700">{$_('description')}</label>
> <input
{$_("group-name-is-required")} autocomplete="off"
</span> placeholder={$_('description')}
{/if} type="text"
</div> bind:value={editable.description}
<div class="text-sm w-full"> name="firstname"
<label for="firstname" class="font-medium text-gray-700" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
>{$_("description")}</label </div>
> <div class="text-sm w-full mt-8">
<input <p class="font-medium mb-4">
autocomplete="off" {$_('permissions')}
placeholder={$_("description")} <a
type="text" class="px-4 py-2 bg-gray-500 rounded-md text-white"
bind:value={editable.description} href="/groups/{params.groupid}/permissions/">{$_('edit-permissions')}</a>
name="firstname" </p>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" <div class="w-full sm:my-px sm:px-px sm:w-1/2">
/> <input
</div> autocomplete="off"
<div class="text-sm w-full mt-8"> placeholder="{$_('search-for-permission')}"
<p class="font-medium mb-4"> type="text"
{$_("permissions")} bind:value={search_permission}
<a 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" />
class="px-4 py-2 bg-gray-500 rounded-md text-white" </div>
href="/groups/{params.groupid}/permissions/" {#each original_data.permissions as p}
>{$_("edit-permissions")}</a {#if p.toLowerCase().includes(search_permission.toLowerCase())}
> <span
</p> style="background:{matched_colors[p.split(':')[0]][0]};color:{matched_colors[p.split(':')[0]][1]};"
<div class="w-full sm:my-px sm:px-px sm:w-1/2"> 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>
<input <!-- -->
autocomplete="off" {/if}
placeholder={$_("search-for-permission")} {/each}
type="text" </div>
bind:value={search_permission} </section>
class="mt-4 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" {:catch error}
/> <PromiseError {error} />
</div> {/await}
{#each original_data.permissions as p}
{#if p.toLowerCase().includes(search_permission.toLowerCase())}
<span
style="background:{matched_colors[
p.split(':')[0]
][0]};color:{matched_colors[p.split(':')[0]][1]};"
class="mt-1 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-indigo-100 rounded"
>{p}</span
>
<!-- -->
{/if}
{/each}
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}

View File

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

View File

@@ -9,22 +9,21 @@
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <span class="mb-1 text-3xl font-extrabold leading-tight">
{$_("user-groups")} {$_('user-groups')}
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:CREATE')}
<button <button
on:click={() => { on:click={() => {
modal_open = true; modal_open = true;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
> {$_('add-user-group')}
{$_("add-user-group")}
</button> </button>
{/if} {/if}
</span> </span>
<UserGroupsOverview bind:current_groups /> <UserGroupsOverview bind:current_groups />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:CREATE')}
<AddGroupModal bind:current_groups bind:modal_open /> <AddGroupModal bind:current_groups bind:modal_open />
{/if} {/if}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,34 +11,32 @@
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <span class="mb-1 text-3xl font-extrabold leading-tight">
{$_("organizations")} {$_('organizations')}
{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:CREATE')}
<button <button
on:click={() => { on:click={() => {
modal_open = true; modal_open = true;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
> {$_('create-organization')}
{$_("create-organization")}
</button> </button>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:IMPORT")} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
<button <button
on:click={() => { on:click={() => {
import_modal_open = true; import_modal_open = true;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
> {$_('import-runners')}
{$_("import-runners")}
</button> </button>
{/if} {/if}
</span> </span>
<OrgOverview bind:current_organizations /> <OrgOverview bind:current_organizations />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:CREATE')}
<AddOrgModal bind:current_organizations bind:modal_open /> <AddOrgModal bind:current_organizations bind:modal_open />
<ImportRunnerModal <ImportRunnerModal
on:cancelDelete={(event) => { on:cancelDelete={(event) => {
@@ -49,6 +47,5 @@
passed_orgs={current_organizations} passed_orgs={current_organizations}
opened_from="OrgOverview" opened_from="OrgOverview"
current_runners={[]} current_runners={[]}
bind:import_modal_open bind:import_modal_open />
/>
{/if} {/if}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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