Compare commits

..

No commits in common. "dev" and "0.1.2-0" have entirely different histories.
dev ... 0.1.2-0

207 changed files with 5871 additions and 27456 deletions

View File

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

41
.drone.yml Normal file
View File

@ -0,0 +1,41 @@
---
kind: pipeline
type: docker
name: build:dev
steps:
- name: build dev
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: DOCKER_REGISTRY_USER
password:
from_secret: DOCKER_REGISTRY_PASSWORD
repo: registry.odit.services/lfk/frontend
tags:
- dev
registry: registry.odit.services
- name: run full license export
depends_on: ["clone"]
image: node:alpine
commands:
- yarn
- yarn licenses:export
- name: push new licenses file to repo
depends_on: ["run full license export"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: true
commit_message: new license file version [CI SKIP]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/frontend.git
ssh_key:
from_secret: GITLAB_SSHKEY
trigger:
branch:
- dev
event:
- push

View File

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

14
.gitignore vendored
View File

@ -1,8 +1,10 @@
.vscode
.idea
node_modules
*.map
dist
dist-ssr
public/env.js
public/index.html
/dist
.pnpm-store
.yarn
.pnp.*
/build
yarn.lock
package-lock.json
*.map

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,19 @@
FROM registry.odit.services/hub/library/node:23.10.0-alpine3.21 AS build
ARG NPM_REGISTRY_URL=https://registry.npmjs.org
FROM node:15.4.0-alpine3.12
WORKDIR /app
COPY package.json pnpm-lock.yaml vite.config.js index.html ./
RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@10.8
RUN mkdir /pnpm && pnpm config set store-dir /pnpm && pnpm i
RUN npm i -g pnpm
COPY package.json ./
RUN pnpm i
COPY package.json *.config.js workbox-config.js ./
COPY src ./src
COPY public ./public
RUN pnpm build
RUN pnpm run build:sw
RUN pnpm run build
# final image
FROM registry.odit.services/library/nginx-brotli:3.15 AS final
COPY --from=build /app/dist /usr/share/nginx/html
FROM alpine
COPY --from=0 /app/build /app
RUN rm -rf /app/build/_dist_/components
RUN rm -rf /app/build/_dist_/locales
RUN rm -rf /app/build-manifest.json
FROM fholzer/nginx-brotli:v1.19.1
COPY --from=1 /app /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf

362
LICENSE
View File

@ -1,362 +0,0 @@
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Creative
Commons Corporation ("Creative Commons") is not a law firm and does not provide
legal services or legal advice. Distribution of Creative Commons public licenses
does not create a lawyer-client or other relationship. Creative Commons makes
its licenses and related information available on an "as-is" basis. Creative
Commons gives no warranties regarding its licenses, any material licensed
under their terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the fullest
extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and conditions
that creators and other rights holders may use to share original works of
authorship and other material subject to copyright and certain other rights
specified in the public license below. The following considerations are for
informational purposes only, are not exhaustive, and do not form part of our
licenses.
Considerations for licensors: Our public licenses are intended for use by
those authorized to give the public permission to use material in ways otherwise
restricted by copyright and certain other rights. Our licenses are irrevocable.
Licensors should read and understand the terms and conditions of the license
they choose before applying it. Licensors should also secure all rights necessary
before applying our licenses so that the public can reuse the material as
expected. Licensors should clearly mark any material not subject to the license.
This includes other CC-licensed material, or material used under an exception
or limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public licenses, a licensor
grants the public permission to use the licensed material under specified
terms and conditions. If the licensor's permission is not necessary for any
reasonfor example, because of any applicable exception or limitation to copyrightthen
that use is not regulated by the license. Our licenses grant only permissions
under copyright and certain other rights that a licensor has authority to
grant. Use of the licensed material may still be restricted for other reasons,
including because others have copyright or other rights in the material. A
licensor may make special requests, such as asking that all changes be marked
or described. Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations for the public
: wiki.creativecommons.org/Considerations_for_licensees
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public
License
By exercising the Licensed Rights (defined below), You accept and agree to
be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike
4.0 International Public License ("Public License"). To the extent this Public
License may be interpreted as a contract, You are granted the Licensed Rights
in consideration of Your acceptance of these terms and conditions, and the
Licensor grants You such rights in consideration of benefits the Licensor
receives from making the Licensed Material available under these terms and
conditions.
Section 1 Definitions.
a. Adapted Material means material subject to Copyright and Similar Rights
that is derived from or based upon the Licensed Material and in which the
Licensed Material is translated, altered, arranged, transformed, or otherwise
modified in a manner requiring permission under the Copyright and Similar
Rights held by the Licensor. For purposes of this Public License, where the
Licensed Material is a musical work, performance, or sound recording, Adapted
Material is always produced where the Licensed Material is synched in timed
relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright and Similar
Rights in Your contributions to Adapted Material in accordance with the terms
and conditions of this Public License.
c. BY-NC-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses,
approved by Creative Commons as essentially the equivalent of this Public
License.
d. Copyright and Similar Rights means copyright and/or similar rights closely
related to copyright including, without limitation, performance, broadcast,
sound recording, and Sui Generis Database Rights, without regard to how the
rights are labeled or categorized. For purposes of this Public License, the
rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
e. Effective Technological Measures means those measures that, in the absence
of proper authority, may not be circumvented under laws fulfilling obligations
under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996,
and/or similar international agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or any other
exception or limitation to Copyright and Similar Rights that applies to Your
use of the Licensed Material.
g. License Elements means the license attributes listed in the name of a Creative
Commons Public License. The License Elements of this Public License are Attribution,
NonCommercial, and ShareAlike.
h. Licensed Material means the artistic or literary work, database, or other
material to which the Licensor applied this Public License.
i. Licensed Rights means the rights granted to You subject to the terms and
conditions of this Public License, which are limited to all Copyright and
Similar Rights that apply to Your use of the Licensed Material and that the
Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights under this
Public License.
k. NonCommercial means not primarily intended for or directed towards commercial
advantage or monetary compensation. For purposes of this Public License, the
exchange of the Licensed Material for other material subject to Copyright
and Similar Rights by digital file-sharing or similar means is NonCommercial
provided there is no payment of monetary compensation in connection with the
exchange.
l. Share means to provide material to the public by any means or process that
requires permission under the Licensed Rights, such as reproduction, public
display, public performance, distribution, dissemination, communication, or
importation, and to make material available to the public including in ways
that members of the public may access the material from a place and at a time
individually chosen by them.
m. Sui Generis Database Rights means rights other than copyright resulting
from Directive 96/9/EC of the European Parliament and of the Council of 11
March 1996 on the legal protection of databases, as amended and/or succeeded,
as well as other essentially equivalent rights anywhere in the world.
n. You means the individual or entity exercising the Licensed Rights under
this Public License. Your has a corresponding meaning.
Section 2 Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License, the Licensor
hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive,
irrevocable license to exercise the Licensed Rights in the Licensed Material
to:
A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial
purposes only; and
B. produce, reproduce, and Share Adapted Material for NonCommercial purposes
only.
2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions
and Limitations apply to Your use, this Public License does not apply, and
You do not need to comply with its terms and conditions.
3. Term. The term of this Public License is specified in Section 6(a).
4. Media and formats; technical modifications allowed. The Licensor authorizes
You to exercise the Licensed Rights in all media and formats whether now known
or hereafter created, and to make technical modifications necessary to do
so. The Licensor waives and/or agrees not to assert any right or authority
to forbid You from making technical modifications necessary to exercise the
Licensed Rights, including technical modifications necessary to circumvent
Effective Technological Measures. For purposes of this Public License, simply
making modifications authorized by this Section 2(a)(4) never produces Adapted
Material.
5. Downstream recipients.
A. Offer from the Licensor Licensed Material. Every recipient of the Licensed
Material automatically receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this Public License.
B. Additional offer from the Licensor Adapted Material. Every recipient
of Adapted Material from You automatically receives an offer from the Licensor
to exercise the Licensed Rights in the Adapted Material under the conditions
of the Adapter's License You apply.
C. No downstream restrictions. You may not offer or impose any additional
or different terms or conditions on, or apply any Effective Technological
Measures to, the Licensed Material if doing so restricts exercise of the Licensed
Rights by any recipient of the Licensed Material.
6. No endorsement. Nothing in this Public License constitutes or may be construed
as permission to assert or imply that You are, or that Your use of the Licensed
Material is, connected with, or sponsored, endorsed, or granted official status
by, the Licensor or others designated to receive attribution as provided in
Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not licensed under this
Public License, nor are publicity, privacy, and/or other similar personality
rights; however, to the extent possible, the Licensor waives and/or agrees
not to assert any such rights held by the Licensor to the limited extent necessary
to allow You to exercise the Licensed Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this Public License.
3. To the extent possible, the Licensor waives any right to collect royalties
from You for the exercise of the Licensed Rights, whether directly or through
a collecting society under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly reserves any right
to collect such royalties, including when the Licensed Material is used other
than for NonCommercial purposes.
Section 3 License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the following
conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified form), You must:
A. retain the following if it is supplied by the Licensor with the Licensed
Material:
i. identification of the creator(s) of the Licensed Material and any others
designated to receive attribution, in any reasonable manner requested by the
Licensor (including by pseudonym if designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of warranties;
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
B. indicate if You modified the Licensed Material and retain an indication
of any previous modifications; and
C. indicate the Licensed Material is licensed under this Public License, and
include the text of, or the URI or hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner
based on the medium, means, and context in which You Share the Licensed Material.
For example, it may be reasonable to satisfy the conditions by providing a
URI or hyperlink to a resource that includes the required information.
3. If requested by the Licensor, You must remove any of the information required
by Section 3(a)(1)(A) to the extent reasonably practicable.
b. ShareAlike.In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons license with
the same License Elements, this version or later, or a BY-NC-SA Compatible
License.
2. You must include the text of, or the URI or hyperlink to, the Adapter's
License You apply. You may satisfy this condition in any reasonable manner
based on the medium, means, and context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms or conditions
on, or apply any Effective Technological Measures to, Adapted Material that
restrict exercise of the rights granted under the Adapter's License You apply.
Section 4 Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that apply to
Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract,
reuse, reproduce, and Share all or a substantial portion of the contents of
the database for NonCommercial purposes only;
b. if You include all or a substantial portion of the database contents in
a database in which You have Sui Generis Database Rights, then the database
in which You have Sui Generis Database Rights (but not its individual contents)
is Adapted Material, including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share all or
a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not replace
Your obligations under this Public License where the Licensed Rights include
other Copyright and Similar Rights.
Section 5 Disclaimer of Warranties and Limitation of Liability.
a. Unless otherwise separately undertaken by the Licensor, to the extent possible,
the Licensor offers the Licensed Material as-is and as-available, and makes
no representations or warranties of any kind concerning the Licensed Material,
whether express, implied, statutory, or other. This includes, without limitation,
warranties of title, merchantability, fitness for a particular purpose, non-infringement,
absence of latent or other defects, accuracy, or the presence or absence of
errors, whether or not known or discoverable. Where disclaimers of warranties
are not allowed in full or in part, this disclaimer may not apply to You.
b. To the extent possible, in no event will the Licensor be liable to You
on any legal theory (including, without limitation, negligence) or otherwise
for any direct, special, indirect, incidental, consequential, punitive, exemplary,
or other losses, costs, expenses, or damages arising out of this Public License
or use of the Licensed Material, even if the Licensor has been advised of
the possibility of such losses, costs, expenses, or damages. Where a limitation
of liability is not allowed in full or in part, this limitation may not apply
to You.
c. The disclaimer of warranties and limitation of liability provided above
shall be interpreted in a manner that, to the extent possible, most closely
approximates an absolute disclaimer and waiver of all liability.
Section 6 Term and Termination.
a. This Public License applies for the term of the Copyright and Similar Rights
licensed here. However, if You fail to comply with this Public License, then
Your rights under this Public License terminate automatically.
b. Where Your right to use the Licensed Material has terminated under Section
6(a), it reinstates:
1. automatically as of the date the violation is cured, provided it is cured
within 30 days of Your discovery of the violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any right the
Licensor may have to seek remedies for Your violations of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the Licensed Material
under separate terms or conditions or stop distributing the Licensed Material
at any time; however, doing so will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
Section 7 Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different terms or
conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the Licensed
Material not stated herein are separate from and independent of the terms
and conditions of this Public License.
Section 8 Interpretation.
a. For the avoidance of doubt, this Public License does not, and shall not
be interpreted to, reduce, limit, restrict, or impose conditions on any use
of the Licensed Material that could lawfully be made without permission under
this Public License.
b. To the extent possible, if any provision of this Public License is deemed
unenforceable, it shall be automatically reformed to the minimum extent necessary
to make it enforceable. If the provision cannot be reformed, it shall be severed
from this Public License without affecting the enforceability of the remaining
terms and conditions.
c. No term or condition of this Public License will be waived and no failure
to comply consented to unless expressly agreed to by the Licensor.
d. Nothing in this Public License constitutes or may be interpreted as a limitation
upon, or waiver of, any privileges and immunities that apply to the Licensor
or You, including from the legal processes of any jurisdiction or authority.
Creative Commons is not a party to its public licenses. Notwithstanding, Creative
Commons may elect to apply one of its public licenses to material it publishes
and in those instances will be considered the "Licensor." The text of the
Creative Commons public licenses is dedicated to the public domain under the
CC0 Public Domain Dedication. Except for the limited purpose of indicating
that material is shared under a Creative Commons public license or as otherwise
permitted by the Creative Commons policies published at creativecommons.org/policies,
Creative Commons does not authorize the use of the trademark "Creative Commons"
or any other trademark or logo of Creative Commons without its prior written
consent including, without limitation, in connection with any unauthorized
modifications to any of its public licenses or any other arrangements, understandings,
or agreements concerning use of licensed material. For the avoidance of doubt,
this paragraph does not form part of the public licenses.
Creative Commons may be contacted at creativecommons.org.

View File

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

View File

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

6
env.sample.js Normal file
View File

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

View File

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

View File

@ -6,24 +6,10 @@ http {
server {
error_page 404 /index.html;
root /usr/share/nginx/html;
location /assets {
expires 1y;
log_not_found off;
access_log off;
}
location = /index.html {
add_header Cache-Control 'no-store';
}
location = / {
add_header Cache-Control 'no-store';
}
location = /env.js {
add_header Cache-Control 'no-store';
}
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(?:ico|css|gif|jpe?g|png)$ {
location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
expires 1y;
add_header Pragma public;
add_header Cache-Control "public";

View File

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

View File

@ -1,67 +1,56 @@
{
"name": "@odit/lfk-frontend",
"version": "1.14.2",
"type": "module",
"scripts": {
"i18n-order": "node order.js",
"dev": "vite",
"format": "prettier --write --plugin-search-dir=. .",
"build": "vite build",
"release": "release-it --only-version",
"licenses:export": "license-exporter --json -o public"
},
"license": "CC-BY-NC-SA-4.0",
"devDependencies": {
"@odit/license-exporter": "0.2.0",
"@sveltejs/vite-plugin-svelte": "2.1.1",
"@types/papaparse": "^5.3.15",
"@types/underscore": "^1.13.0",
"auto-changelog": "2.5.0",
"prettier": "3.5.3",
"prettier-plugin-svelte": "3.3.3",
"release-it": "17.10.0",
"svelte-select": "3.17.0",
"vite": "6.3.2",
"vite-plugin-mkcert": "^1.17.8"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "chore(release): ${version}",
"push": true,
"tag": true,
"tagName": "${version}",
"tagAnnotation": "${version}"
},
"npm": {
"publish": false
},
"hooks": {
"after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js && git add index.html && node order.js && git add src/locales"
}
},
"dependencies": {
"@bwip-js/browser": "^4.6.0",
"@fontsource/athiti": "^5.2.5",
"@odit/lfk-client-js": "1.2.7",
"@paralleldrive/cuid2": "2.2.2",
"@tailwindcss/vite": "^4.1.4",
"@tanstack/svelte-table": "8.9.1",
"check-password-strength": "2.0.10",
"html5-qrcode": "^2.3.8",
"localforage": "1.10.0",
"papaparse": "^5.5.2",
"svelte": "3.58.0",
"svelte-french-toast": "1.2.0",
"svelte-i18n": "4.0.1",
"tailwindcss": "^4.1.4",
"tinro": "0.6.12",
"underscore": "^1.13.7",
"validator": "13.15.0",
"xlsx": "0.18.5"
},
"volta": {
"node": "20.0.0"
}
"name": "@odit/lfk-frontend",
"version": "0.1.2-0",
"scripts": {
"i18n-order": "node order.js",
"dev": "snowpack dev",
"build": "snowpack build",
"build:sw": "workbox generateSW workbox-config.js",
"release": "release-it",
"changelog": "npx auto-changelog --commit-limit false --template https://raw.githubusercontent.com/release-it/release-it/master/templates/changelog-compact.hbs",
"licenses:export": "license-exporter --json -o public"
},
"dependencies": {
"@odit/lfk-client-js": "0.0.10",
"filepond": "4.25.1",
"gridjs": "3.2.1",
"localforage": "1.9.0",
"svelte-filepond": "0.0.1",
"svelte-focus-trap": "1.0.1",
"svelte-i18n": "3.3.0",
"tailwindcss": "2.0.2",
"tinro": "0.5.6",
"toastify-js": "1.9.3",
"validator": "13.5.2"
},
"devDependencies": {
"@odit/license-exporter": "0.0.9",
"@snowpack/plugin-svelte": "3.4.1",
"auto-changelog": "^2.2.1",
"autoprefixer": "10.2.1",
"postcss": "8.2.4",
"postcss-load-config": "3.0.0",
"release-it": "^14.2.2",
"snowpack": "3.0.0-rc.2",
"svelte": "3.31.2",
"svelte-preprocess": "4.6.1",
"workbox-cli": "6.0.2"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀RELEASE v${version}",
"push": false,
"tag": true,
"tagName": null,
"tagAnnotation": "v${version}"
},
"npm": {
"publish": false
},
"hooks": {
"after:bump": "npx auto-changelog -p"
}
}
}

4154
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

View File

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

Binary file not shown.

21
public/index.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/lfk-logo.png">
<meta name="theme-color" content="#FFFFFF">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Lauf Für Kaya! - Admin" />
<title>Lauf für Kaya! - Admin</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script>
<script defer type="module" src="/_dist_/index.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

4
public/logo.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

2
public/sw.js Normal file
View File

@ -0,0 +1,2 @@
if(!self.define){const e=e=>{"require"!==e&&(e+=".js");let r=Promise.resolve();return i[e]||(r=new Promise((async r=>{if("document"in self){const i=document.createElement("script");i.src=e,document.head.appendChild(i),i.onload=r}else importScripts(e),r()}))),r.then((()=>{if(!i[e])throw new Error(`Module ${e} didnt register its module`);return i[e]}))},r=(r,i)=>{Promise.all(r.map(e)).then((e=>i(1===e.length?e[0]:e)))},i={require:Promise.resolve(r)};self.define=(r,s,o)=>{i[r]||(i[r]=Promise.resolve().then((()=>{let i={};const c={uri:location.origin+r.slice(1)};return Promise.all(s.map((r=>{switch(r){case"exports":return i;case"module":return c;default:return e(r)}}))).then((e=>{const r=o(...e);return i.default||(i.default=r),i}))})))}}define("./sw.js",["./workbox-c8ead010"],(function(e){"use strict";self.addEventListener("message",(e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()})),e.precacheAndRoute([{url:"favicon.ico",revision:"ba44f340afba5bb1a07f14decc15dd04"},{url:"favicon.png",revision:"07a9941cec62319578fa2a1734db9959"},{url:"favicon.svg",revision:"689d6c6fda51e359c0e5725d9e905064"},{url:"index.html",revision:"931c34f3675364dcc09411aa0f223776"},{url:"logo.svg",revision:"4c9e31a1f4268d7e36e22cda7656e561"},{url:"manifest.webmanifest",revision:"75c93eb352c4877216e77b1d7f73445f"},{url:"robots.txt",revision:"61c27d2cd39a713f7829422c3d9edcc7"}],{})}));
//# sourceMappingURL=sw.js.map

File diff suppressed because one or more lines are too long

30
snowpack.config.js Normal file
View File

@ -0,0 +1,30 @@
/** @type {import("snowpack").SnowpackUserConfig } */
module.exports = {
mount: {
public: '/',
src: '/_dist_'
},
plugins: [ '@snowpack/plugin-svelte' ],
install: [
/* ... */
],
installOptions: {
/* ... */
sourceMap: false
},
devOptions: {
/* ... */
},
buildOptions: {
/* ... */
},
proxy: {
/* ... */
},
alias: {
/* ... */
},
experiments: {
optimize: { bundle: true, minify: true }
}
};

View File

@ -1,4 +1,6 @@
<script>
import "./TailwindStyles.svelte";
import "toastify-js/src/toastify.css";
import { Route, router } from "tinro";
router.subscribe((routeInfo) => {
window.scrollTo(0, 0);
@ -22,70 +24,40 @@
name: "lfk_admin",
version: 1.0,
storeName: "lfk_admin",
description: "LfK! admin dashboard",
description: "LfK! admin dashbaord",
});
window.onunhandledrejection = (event) => {
if (event.reason.toString() == "Error: Unauthorized") {
localForage.clear();
location.replace("/");
}
};
//
import Login from "./components/auth/Login.svelte";
import Dashboard from "./components/dashboard/Dashboard.svelte";
import Login from "./components/Login.svelte";
import Dashboard from "./components/Dashboard.svelte";
import store from "./store.js";
import ForgotPassword from "./components/auth/ForgotPassword.svelte";
import MainDashContent from "./components/dashboard/MainDashContent.svelte";
import Users from "./components/users/Users.svelte";
import About from "./components/general/About.svelte";
import Settings from "./components/settings/Settings.svelte";
import Transition from "./components/base/Transition.svelte";
import Orgs from "./components/orgs/Orgs.svelte";
import CardAssignment from "./components/tools/CardAssignment.svelte";
import Runners from "./components/runners/Runners.svelte";
import Footer from "./components/general/Footer.svelte";
import TracksOverview from "./components/tracks/TracksOverview.svelte";
import OrgDetail from "./components/orgs/OrgDetail.svelte";
import Teams from "./components/teams/Teams.svelte";
import { OpenAPI } from "@odit/lfk-client-js";
import UserDetail from "./components/users/UserDetail.svelte";
import NotFound from "./components/NotFound.svelte";
import ForgotPassword from "./components/ForgotPassword.svelte";
import MainDashContent from "./components/MainDashContent.svelte";
import Users from "./components/Users.svelte";
import About from "./components/About.svelte";
import Settings from "./components/Settings.svelte";
import Transition from "./components/Transition.svelte";
import Orgs from "./components/Orgs.svelte";
import Runners from "./components/Runners.svelte";
import Footer from "./components/Footer.svelte";
import Tracks from "./components/Tracks.svelte";
import TracksOverview from "./components/TracksOverview.svelte";
import OrgDetail from "./components/OrgDetail.svelte";
import Teams from "./components/Teams.svelte";
import { OpenAPI, AuthService } from "@odit/lfk-client-js";
import UserDetail from "./components/UserDetail.svelte";
OpenAPI.BASE = config.baseurl;
import TeamDetail from "./components/teams/TeamDetail.svelte";
import UserPermissions from "./components/users/UserPermissions.svelte";
import GroupPermissions from "./components/groups/GroupPermissions.svelte";
import RunnerDetail from "./components/runners/RunnerDetail.svelte";
import ResetPassword from "./components/auth/ResetPassword.svelte";
import Contacts from "./components/contacts/Contacts.svelte";
import ContactDetail from "./components/contacts/ContactDetail.svelte";
import Donors from "./components/donors/Donors.svelte";
import Groups from "./components/groups/Groups.svelte";
import DonorDetail from "./components/donors/DonorDetail.svelte";
import Donations from "./components/donations/Donations.svelte";
import DonationDetail from "./components/donations/DonationDetail.svelte";
import GroupDetail from "./components/groups/GroupDetail.svelte";
import ScanStations from "./components/scanstations/ScanStations.svelte";
import ScanStationDetail from "./components/scanstations/ScanStationDetail.svelte";
import Scans from "./components/scans/Scans.svelte";
import ScanDetail from "./components/scans/ScanDetail.svelte";
import Cards from "./components/cards/Cards.svelte";
import StatsClients from "./components/statsclients/StatsClients.svelte";
import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte";
import CardReplacement from "./components/tools/CardReplacement.svelte";
import ScanClient from "./components/tools/ScanClient.svelte";
import DonationCreate from "./components/tools/DonationCreate.svelte";
import { register as registerSW } from "./swmodule";
store.init();
// registerSW();
</script>
<Route>
{#if $router.path === "/forgot_password"}
{#if $router.path === '/forgot_password'}
<Route path="/forgot_password">
<ForgotPassword />
</Route>
{:else if $router.path.includes("/reset")}
<Route path="/reset/:resetkey" let:params>
<ResetPassword {params} />
</Route>
{:else if $router.path === "/about"}
{:else if $router.path === '/about'}
<Route path="/about">
<About />
</Route>
@ -99,26 +71,8 @@
<Route path="/">
<Users />
</Route>
<Route path="/:userid/*" let:params>
<Route path="/">
<UserDetail {params} />
</Route>
<Route path="/permissions/">
<UserPermissions {params} />
</Route>
</Route>
</Route>
<Route path="/groups/*">
<Route path="/">
<Groups />
</Route>
<Route path="/:groupid/*" let:params>
<Route path="/">
<GroupDetail {params} />
</Route>
<Route path="/permissions/">
<GroupPermissions {params} />
</Route>
<Route path="/:userid" let:params>
<UserDetail {params} />
</Route>
</Route>
<Route path="/tracks/*">
@ -127,43 +81,11 @@
</Route>
<Route path="/:trackid" let:params />
</Route>
<Route path="/runners/*">
<Route path="/" let:meta>
<Runners created_via={meta.query.created_via} />
</Route>
<Route path="/:runnerid" let:params>
<RunnerDetail {params} />
</Route>
<Route path="/runners">
<Runners />
</Route>
<Route path="/tools/*">
<Route path="/cardassignment/">
<CardAssignment />
</Route>
<Route path="/cardreplacement/">
<CardReplacement />
</Route>
<Route path="/scanclient/">
<ScanClient />
</Route>
<Route path="/donationcreate/">
<DonationCreate />
</Route>
</Route>
<Route path="/teams/*">
<Route path="/">
<Teams />
</Route>
<Route path="/:teamid" let:params>
<TeamDetail {params} />
</Route>
</Route>
<Route path="/contacts/*">
<Route path="/">
<Contacts />
</Route>
<Route path="/:contact" let:params>
<ContactDetail {params} />
</Route>
<Route path="/teams">
<Teams />
</Route>
<Route path="/orgs/*">
<Route path="/">
@ -173,54 +95,6 @@
<OrgDetail {params} />
</Route>
</Route>
<Route path="/donors/*">
<Route path="/">
<Donors />
</Route>
<Route path="/:donorid" let:params>
<DonorDetail {params} />
</Route>
</Route>
<Route path="/donations/*">
<Route path="/">
<Donations />
</Route>
<Route path="/:donationid" let:params>
<DonationDetail {params} />
</Route>
</Route>
<Route path="/cards/*">
<Route path="/">
<Cards />
</Route>
<!-- <Route path="/:scanid" let:params>
<ScanDetail {params} />
</Route> -->
</Route>
<Route path="/scans/*">
<Route path="/">
<Scans />
</Route>
<Route path="/:scanid" let:params>
<ScanDetail {params} />
</Route>
</Route>
<Route path="/scanstations/*">
<Route path="/">
<ScanStations />
</Route>
<Route path="/:stationid" let:params>
<ScanStationDetail {params} />
</Route>
</Route>
<Route path="/statsclients/*">
<Route path="/">
<StatsClients />
</Route>
<Route path="/:clientid" let:params>
<StatsClientDetail {params} />
</Route>
</Route>
<Route path="/about">
<About />
</Route>

View File

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

197
src/components/About.svelte Normal file
View File

@ -0,0 +1,197 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "./outsideclick";
import { focusTrap } from "svelte-focus-trap";
export let modal_open;
(function () {
document.onkeydown = function (e) {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
};
})();
const license_promise = fetch("/licenses.json");
let licenses = [];
$: currentlicense = "";
$: licensetext = "";
license_promise
.then((response) => response.json())
.then((json) => {
licenses = json;
});
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="none"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium">
{$_('read-license')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">{currentlicense}</p>
</div>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">{licensetext}</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
Close
</button>
</div>
</div>
</div>
</div>
{/if}
<!-- /// -->
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8">
<h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl">
{$_('about')}
🧾
</h1>
<p
class="mt-2 max-w-xl mx-auto text-xl lg:max-w-3xl lg:text-2xl text-gray-300">
Lauf für Kaya!
<strong class="text-white font-medium">
{$_('by')}
<a href="https://odit.services" class="underline">ODIT.Services</a>
</strong>
<br />
<span class="text-lg">{$_('lfk-is-os')}</span>
</p>
</div>
</div>
<div class="pt-0 pb-16 overflow-hidden lg:pt-12 lg:py-24">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<h2 class="text-4xl font-display font-semibold md:text-5xl">
{$_('credits')}
</h2>
<div
class="max-w-3xl mx-auto text-xl leading-8 font-medium mt-8">
<p class="text-center">{$_('oss_credit_description')}</p>
</div>
<div class="w-screen leading-8 pl-5 mt-5">
{#await license_promise}
<p class="text-center w-full">Licenses are being loaded...</p>
{:then}
<table>
<thead>
<tr>
<th>{$_('dependency_name')}</th>
<th>{$_('license')}</th>
<th>{$_('repo_link')}</th>
<th>{$_('installed-version')}</th>
<th>{$_('author')}</th>
</tr>
</thead>
<tbody>
{#each licenses as l}
<tr>
<td>{l.name}</td>
<td>
{l.license || '?'}<br /><span
class="underline cursor-pointer"
on:click={() => {
modal_open = true;
currentlicense = l.name + '@' + l.version;
licensetext = l.licensetext || $_('no-license-text-could-be-found');
}}>{$_('read-license')}</span>
</td>
<td>
{(l.repo?.url || l.repo)
.replace('git+', '')
.replace('git://', '')}
</td>
<td>{l.version || '?'}</td>
<td>{l.author?.name || l.author || '?'}</td>
</tr>
{/each}
</tbody>
</table>
{:catch error}
<div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
</div>
<h2 class="text-4xl font-display font-semibold md:text-5xl">
Fragen
</h2>
<div class="mt-6 border-t-2 border-gray-100 pt-10">
<dl class="md:grid md:grid-cols-2 md:gap-8">
<div>
<div>
<dt class="text-lg leading-6 font-medium">Q</dt>
<dd class="mt-2">
<p class="text-base text-gray-500">A</p>
</dd>
</div>
</div>
<div class="mt-12 sm:mt-0">
<div id="team-pricing">
<dt class="text-lg leading-6 font-medium">Q</dt>
<dd class="mt-2">
<p class="text-base text-gray-500">A</p>
</dd>
</div>
<div class="mt-12">
<dt class="text-lg leading-6 font-medium">Q</dt>
<dd class="mt-2">
<p class="text-base text-gray-500">A</p>
</dd>
</div>
</div>
</dl>
</div>
</div>
</div>

View File

@ -1,11 +1,11 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { tracks as tracksstore } from "../../store.js";
import { clickOutside } from "./outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { tracks as tracksstore } from "../store.js";
import { TrackService } from "@odit/lfk-client-js";
import toast from "svelte-french-toast";
import Toastify from "toastify-js";
import "toastify-js/src/toastify.css";
export let modal_open;
let trackname_input;
function focus(el) {
@ -37,7 +37,10 @@
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("track-is-being-added"));
const toast = Toastify({
text: $_("track-is-being-added"),
duration: -1,
}).showToast();
TrackService.trackControllerPost({
distance: parseInt(tracklength),
name: trackname_input_value,
@ -48,8 +51,12 @@
track_min_duration = 0;
tracklength = 0;
modal_open = false;
toast.dismiss();
toast.success($_("track-added"));
//
Toastify({
text: $_("track-added"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
let storeval = [];
tracksstore.subscribe((val) => {
storeval = val;
@ -57,9 +64,13 @@
storeval.push(result);
tracksstore.set(storeval);
})
.catch((err) => {})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
@ -67,71 +78,59 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
}}>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw] relative z-10"
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="size-6 text-blue-600"
class="h-6 w-6 text-blue-600"
fill="none"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"
><path
viewBox="0 0 640 512"><path
fill="currentColor"
d="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
>
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>
</div>
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-track")}
{$_('create-a-new-track')}
</h3>
<div class="mb-6">
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-required-information-to-add-a-new-track"
)}
{$_('please-provide-the-required-information-to-add-a-new-track')}
</p>
</div>
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700"
>{$_("track-name")}</label
>
class="block text-sm font-medium text-gray-700">{$_('track-name')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_("track-name")}
placeholder={$_('track-name')}
class:border-red-500={isTracknameValid}
class:focus:border-red-500={isTracknameValid}
class:focus:ring-red-500={isTracknameValid}
@ -139,22 +138,18 @@
bind:this={trackname_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if isTracknameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("track-name-must-not-be-empty")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
Track name must not be empty
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="track_length_m"
class="block text-sm font-medium text-gray-700"
>{$_("track-length-in-m")}</label
>
class="block text-sm font-medium text-gray-700">{$_('track-length-in-m')}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
@ -164,28 +159,22 @@
bind:value={tracklength}
type="number"
name="track_length_m"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 p-2"
placeholder="1000"
/>
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="1000" />
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
>m</span
>
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">m</span>
</div>
{#if isTracklengthValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("track-length-must-be-greater-than-0")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
Track length must be greater than 0
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="track_min_duration"
class="block text-sm font-medium text-gray-700"
>{$_("minimum-lap-time-in-s")}</label
>
class="block text-sm font-medium text-gray-700">{$_('minimum-lap-time-in-s')}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
@ -195,19 +184,15 @@
bind:value={track_min_duration}
type="number"
name="track_min_duration"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 p-2"
placeholder={smart_track_min_duration_placeholder}
/>
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={smart_track_min_duration_placeholder} />
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
>s</span
>
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">s</span>
</div>
{#if !trackMintimevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("minimum-lap-time-must-be-a-positive-number-or-0")}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
minimum lap time must be a positive number or 0
</span>
{/if}
</div>
@ -215,24 +200,22 @@
</div>
</div>
</div>
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create")}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
Create
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="cancel_modal_button"
>
{$_("cancel")}
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel
</button>
</div>
</div>

View File

@ -0,0 +1,264 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "./outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { tracks as tracksstore } from "../store.js";
import { TrackService, UserService } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail";
import Toastify from "toastify-js";
import "toastify-js/src/toastify.css";
import About from "./About.svelte";
export let modal_open;
let firstname_input;
let lastname_input;
let middlename_input;
let password_input;
let email_input;
function focus(el) {
el.focus();
}
$: middlename_input_value = "";
$: password_input_value = "";
$: email_input_value = "";
$: lastname_input_value = "";
$: firstname_input_value = "";
$: track_min_duration = 0;
$: tracklength = 0;
$: processed_last_submit = true;
$: isPasswordValid = password_input_value.trim().length === 0;
$: isEmailValid = isEmail(email_input_value);
$: isLastnameValid = lastname_input_value.trim().length === 0;
$: isFirstnameValid = firstname_input_value.trim().length === 0;
$: createbtnenabled = !isFirstnameValid && !isLastnameValid;
(function () {
document.onkeydown = function (e) {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: "User is being added...",
duration: -1,
}).showToast();
UserService.userControllerPost({
firstname: firstname_input_value,
lastname: lastname_input_value,
middlename: middlename_input_value,
email:email_input_value,password:password_input_value
})
.then((result) => {
firstname_input_value = "";
lastname_input_value = "";
middlename_input_value = "";
modal_open = false;
//
Toastify({
text: "User added",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
let storeval = [];
tracksstore.subscribe((val) => {
storeval = val;
});
storeval.push(result);
tracksstore.set(storeval);
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Create a new User
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
Please provide the required information to add a new user.
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700">First Name</label>
<input
use:focus
autocomplete="off"
placeholder="First Name"
class:border-red-500={isFirstnameValid}
class:focus:border-red-500={isFirstnameValid}
class:focus:ring-red-500={isFirstnameValid}
bind:value={firstname_input_value}
bind:this={firstname_input}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
First Name is required
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700">Middle Name</label>
<input
autocomplete="off"
placeholder="Middle Name"
bind:value={middlename_input_value}
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="lastname"
class="block text-sm font-medium text-gray-700">Last Name</label>
<input
autocomplete="off"
placeholder="Last Name"
class:border-red-500={isLastnameValid}
class:focus:border-red-500={isLastnameValid}
class:focus:ring-red-500={isLastnameValid}
bind:value={lastname_input_value}
bind:this={lastname_input}
type="text"
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
Last Name is required
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="password"
class="block text-sm font-medium text-gray-700">Password</label>
<input
autocomplete="off"
placeholder="Password"
class:border-red-500={isPasswordValid}
class:focus:border-red-500={isPasswordValid}
class:focus:ring-red-500={isPasswordValid}
bind:value={password_input_value}
bind:this={password_input}
type="password"
name="password"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block 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 isPasswordValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
Password is required
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="email"
class="block text-sm font-medium text-gray-700">E-Mail</label>
<input
autocomplete="off"
placeholder="E-Mail"
class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid}
bind:value={email_input_value}
bind:this={email_input}
type="email"
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
valid email is required
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
Create
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel
</button>
</div>
</div>
</div>
</div>
{/if}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,75 @@
<script>
import Avatars from "./Avatars.svelte";
import Badges from "./Badges.svelte";
import BreadcrumbNav from "./BreadcrumbNav.svelte";
import FileUpload from "./FileUpload.svelte";
import Pagination from "./Pagination.svelte";
import Table from "./Table.svelte";
import Tabs from "./Tabs.svelte";
import Tags from "./Tags.svelte";
</script>
<div class="border-4 border-dashed rounded h-96 mb-4" />
<div class="mb-8">
<FileUpload />
</div>
<div class="mb-8">
<Tabs />
</div>
<div class="mb-8">
<Tags />
</div>
<div class="mb-8">
<Badges />
</div>
<div class="mb-8">
<Avatars />
</div>
<Pagination />
<div class="mb-8">
<Table />
</div>
<div
class="widget w-full p-4 mb-4 rounded-lg bg-white border border-grey-100 dark:bg-grey-895 dark:border-grey-890">
<div class="flex flex-row items-center justify-between mb-6">
<div class="flex flex-col">
<div class="text-sm font-light text-grey-500">Regular</div>
<div class="text-sm font-bold"><span>Text inputs</span></div>
</div>
</div>
<div class="flex flex-col lg:flex-row lg:flex-wrap w-full lg:space-x-4">
<div class="w-full lg:w-1/4">
<div class="form-element ">
<div class="form-label">Label</div><input
name="name"
type="text"
class="form-input"
placeholder="Enter something..." />
<div class="form-hint">This is a hint</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div class="form-element ">
<div class="form-label">First name</div><input
name="name"
type="text"
class="form-input form-input-invalid"
placeholder="john@example.com" />
<div class="form-error">First name is required</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div class="form-element ">
<div class="form-label">First name</div><input
name="name"
type="text"
class="form-input form-input-valid"
placeholder="john@example.com" />
<div class="form-success">First name is valid</div>
</div>
</div>
</div>
</div>
<div class="mb-8">
<BreadcrumbNav />
</div>

View File

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

1852
src/components/Dash.svelte Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,359 @@
<script>
import { _ } from "svelte-i18n";
import { active } from "tinro";
import localForage from "localforage";
import { router } from "tinro";
import store from "../store";
import NoComponentLoaded from "./NoComponentLoaded.svelte";
import { AuthService, OpenAPI } from "@odit/lfk-client-js";
let activePage = "dashboard";
let dropdown1 = false;
let navOpen = false;
function ismobile() {
let check = false;
(function (a) {
if (
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
a
) ||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
a.substr(0, 4)
)
)
check = true;
})(navigator.userAgent || navigator.vendor || window.opera);
return check;
}
$: mobile = ismobile();
function logout() {
localForage.clear();
location.replace("/");
}
</script>
<section class="min-h-screen bg-gray-50 dark:bg-black dark:text-gray-100">
<nav
class:hidden={!navOpen && mobile}
class="select-none fixed top-0 left-0 z-20 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 md:translate-x-0">
<a href="/" class="flex items-center px-4 py-5">
<img
src="/lfk-logo.png"
alt="Logo"
class="h-10" />
<h3 class="text-lg">Lauf für Kaya! Admin</h3>
</a>
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation">
<a
class:dark:bg-gray-900={$router.path === '/'}
class:bg-gray-100={$router.path === '/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" />
</svg>
<span>{$_('dashboard-title')}</span>
</a>
<a
class:dark:bg-gray-900={$router.path.includes('/orgs/')}
class:bg-gray-100={$router.path.includes('/orgs/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/orgs/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg>
<span>{$_('orgs')}</span>
</a>
<a
class:dark:bg-gray-900={$router.path === '/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"
href="/users/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('users')}</span>
</a>
<a
class:dark:bg-gray-900={$router.path === '/runners/'}
class:bg-gray-100={$router.path === '/runners/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/runners/">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
<span>{$_('runners')}</span>
</a>
<a
class:dark:bg-gray-900={$router.path === '/teams/'}
class:bg-gray-100={$router.path === '/teams/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/teams/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('teams')}</span>
</a>
<a
class:dark:bg-gray-900={$router.path === '/tracks/'}
class:bg-gray-100={$router.path === '/tracks/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/tracks/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" /></svg>
<span>{$_('tracks')}</span>
</a>
<a
class:dark:bg-gray-900={activePage === 'blub'}
class:bg-gray-100={activePage === 'blub'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="#">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z" />
<path
fill-rule="evenodd"
d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm9.707 5.707a1 1 0 00-1.414-1.414L9 12.586l-1.293-1.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
<span>Checklists</span>
</a>
<div>
<div
tabindex="0"
class:dark:bg-gray-900={activePage === 'blub'}
class:bg-gray-100={activePage === 'blub'}
class="flex items-center justify-between px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
role="button"
on:click={() => {
dropdown1 = !dropdown1;
}}>
<div class="flex items-center">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
fill-rule="evenodd"
d="M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z"
clip-rule="evenodd" />
</svg>
<span>Integrations</span>
</div>
{#if dropdown1}
<svg
class="flex-shrink-0 w-4 h-4 ml-2 transition transform"
xmlns="http://www.w3.org/2000/svg"
style="transform:rotate(90deg)"
viewBox="0 0 20 20"
fill="currentColor">
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd" />
</svg>
{:else}
<svg
class="flex-shrink-0 w-4 h-4 ml-2 transition transform"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd" />
</svg>
{/if}
</div>
{#if dropdown1}
<div class="mb-4">
<a
class="flex items-center py-2 pl-12 pr-4 transition cursor-pointer hover:bg-gray-100 hover:text-gray-900"
href="#">Shopify</a>
<a
class="flex items-center py-2 pl-12 pr-4 transition cursor-pointer hover:bg-gray-100 hover:text-gray-900"
href="#">Slack</a>
<a
class="flex items-center py-2 pl-12 pr-4 transition cursor-pointer hover:bg-gray-100 hover:text-gray-900"
href="#">Zapier</a>
</div>
{/if}
</div>
<a
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="#">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
fill-rule="evenodd"
d="M5 5a3 3 0 015-2.236A3 3 0 0114.83 6H16a2 2 0 110 4h-5V9a1 1 0 10-2 0v1H4a2 2 0 110-4h1.17C5.06 5.687 5 5.35 5 5zm4 1V5a1 1 0 10-1 1h1zm3 0a1 1 0 10-1-1v1h1z"
clip-rule="evenodd" />
<path d="M9 11H3v5a2 2 0 002 2h4v-7zM11 18h4a2 2 0 002-2v-5h-6v7z" />
</svg>
<span>{$_('changelog')}</span>
</a>
<a
class:dark:bg-gray-900={$router.path === '/settings/'}
class:bg-gray-100={$router.path === '/settings/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/settings/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
fill-rule="evenodd"
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
clip-rule="evenodd" />
</svg>
<span>{$_('settings')}</span>
</a>
<a
class:dark:bg-gray-900={$router.path === '/about/'}
class:bg-gray-100={$router.path === '/about/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/about/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
<path d="M12 16v-4M12 8h.01" /></svg>
<span>{$_('about')}</span>
</a>
<span
tabindex="0"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
on:click={() => {
AuthService.authControllerLogout();
logout();
}}>
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z" /></svg>
<span>{$_('logout')}</span>
</span>
</nav>
</nav>
<div class="ml-0 transition md:ml-60">
<header
class="flex items-center justify-between w-full px-4 border-b h-14">
<button
on:click={() => {
navOpen = !navOpen;
}}
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 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clip-rule="evenodd" />
</svg>
</button>
<!-- <div class="hidden -ml-3 form-icon md:block w-96">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<input
class="border-0 form-input"
placeholder="Search for articles..." />
</div> -->
<div class="flex items-end">
<a href="#" class="flex text-gray-500">
<svg
class="flex-shrink-0 w-5 h-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z" />
</svg>
</a>
<a href="/profile/" class="ml-4">
<img
class="h-8 w-8 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt="Profile Picture" />
</a>
</div>
</header>
<slot>
<NoComponentLoaded />
</slot>
</div>
<!-- Sidebar Backdrop -->
<div
on:click={() => {
navOpen = false;
}}
class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" />
</section>

View File

@ -0,0 +1,31 @@
<script>
import { _, json } from "svelte-i18n";
import { getlang } from "./datatable_i18n";
import { Grid } from "gridjs";
import "gridjs/dist/theme/mermaid.css";
//
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

@ -0,0 +1,25 @@
<script>
import "filepond/dist/filepond.css";
import FilePond from "svelte-filepond";
let pond;
// pond.getFiles() will return the active files
// the name to use for the internal file input
let name = "filepond";
function handleInit() {
// console.log("FilePond has initialised");
}
function handleAddFile(err, fileItem) {
// console.log("A file has been added", fileItem);
}
</script>
<div class="app">
<FilePond
bind:this={pond}
{name}
server="/api"
allowMultiple={false}
credits={false}
oninit={handleInit}
onaddfile={handleAddFile} />
</div>

View File

@ -0,0 +1,15 @@
<script>
import { _ } from "svelte-i18n";
</script>
<footer class="container">
<hr class="mt-2 mb-4 border-b-1 border-gray-300" />
<p class="text-sm text-gray-500 mt-4">
Lauf für Kaya! Läufersystem - Copyright © 2020 + proudly powered by
<a
class="underline"
href="https://odit.services"
rel="noopener,noreferrer"
target="_blank">ODIT.Services</a>
</p>
</footer>

View File

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

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

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

View File

@ -0,0 +1,97 @@
<script>
import store from "../store.js";
store.init();
const login = () => {
store.login();
};
</script>
<div
class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-md w-full space-y-8">
<div>
<img
class="mx-auto h-12 w-auto"
src="/lfk-logo.png"
alt="" />
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to your account
</h2>
<p class="mt-2 text-center text-sm text-gray-600">
Or
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">
start your 14-day free trial
</a>
</p>
</div>
<div>
<input type="hidden" name="remember" value="true" />
<div class="rounded-md shadow-sm -space-y-px">
<div>
<label for="email-address" class="sr-only">Email address</label>
<input
id="email-address"
name="email"
type="email"
autocomplete="email"
required
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Email address" />
</div>
<div>
<label for="password" class="sr-only">Password</label>
<input
id="password"
name="password"
type="password"
autocomplete="current-password"
required
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Password" />
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<input
id="remember_me"
name="remember_me"
type="checkbox"
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" />
<label for="remember_me" class="ml-2 block text-sm text-gray-900">
Remember me
</label>
</div>
<div class="text-sm">
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">
Forgot your password?
</a>
</div>
</div>
<div>
<button
on:click="{login}"
type="submit"
class="group relative w-full flex justify-center py-2 px-4 border border-transparent 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">
<span class="absolute left-0 inset-y-0 flex items-center pl-3">
<!-- Heroicon name: lock-closed -->
<svg
class="h-5 w-5 text-indigo-500 group-hover:text-indigo-400"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true">
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd" />
</svg>
</span>
Sign in
</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
<script>
import { _ } from "svelte-i18n";
import StatCards from "./StatCards.svelte";
import store from "../store";
import ComponentDump from "./ComponentDump.svelte";
let navOpen = false;
</script>
<div
class="p-5 overflow-x-hidden"
on:click={() => {
navOpen = false;
}}>
<!-- <div class="border-4 border-dashed rounded h-96" /> -->
<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}</span>
👋</span>
</h1>
<StatCards />
<ComponentDump />
</div>

View File

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

View File

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

View File

@ -0,0 +1,83 @@
<script>
export let params;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
Orgs
</span>
<p class="mb-8 text-lg text-gray-500">
configure the tracks & minimum lap times
</p>
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="mr-2 flex items-center">
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><path
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" /></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="/">Home</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="mr-2 flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M21 20h2v2H1v-2h2V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v17zm-2 0V4H5v16h14zM8 11h3v2H8v-2zm0-4h3v2H8V7zm0 8h3v2H8v-2zm5 0h3v2h-3v-2zm0-4h3v2h-3v-2zm0-4h3v2h-3V7z" /></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">Orgs</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">Org-Details #{params.orgid}</span>
</li>
</ol>
</nav>
</div>
</div>
</section>

View File

@ -0,0 +1,13 @@
<script>
import { _ } from "svelte-i18n";
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
Orgs
</span>
<p class="mb-8 text-lg text-gray-500">
add, delete, edit organizations
</p>
<nav><a class="underline" href="./1">Org 1</a></nav>
</section>

View File

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

View File

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

View File

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

View File

@ -0,0 +1,10 @@
<script>
import { _ } from "svelte-i18n";
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('runners')}
</span>
<p class="mb-8 text-lg text-gray-500">läuft bei ihnen</p>
</section>

View File

@ -0,0 +1,35 @@
<script>
import { _ } from "svelte-i18n";
import FormLayout from "./FormLayout.svelte";
</script>
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8">
<h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl">
🔨<br />{$_('settings')}
</h1>
<p
class="mt-2 max-w-xl mx-auto text-xl lg:max-w-3xl lg:text-2xl text-gray-300">
<span class="text-lg">configure your profile however you want</span>
</p>
</div>
</div>
<div class="pt-0 pb-16 bg-gray-50 overflow-hidden lg:pt-12 lg:py-24">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<!-- <h2 class="text-4xl font-display font-semibold text-gray-900 md:text-5xl">
General
</h2> -->
<div
class="max-w-3xl mx-auto text-xl leading-8 font-medium text-gray-900 mb-16">
<p class="text-center">
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Temporibus et
amet voluptate nulla accusantium vero blanditiis nobis facere veritatis.
Impedit deserunt saepe aliquid unde consequuntur officia consequatur
fugit iusto dolorem?
</p>
</div>
<FormLayout />
</div>
</div>

View File

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

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 dark:bg-grey-895 dark:border-grey-890 dark:bg-gray-900 dark:text-white">
<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 dark:bg-grey-895 dark:border-grey-890 dark:bg-gray-900 dark:text-white">
<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 dark:bg-grey-895 dark:border-grey-890 dark:bg-gray-900 dark:text-white">
<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 dark:bg-grey-895 dark:border-grey-890 dark:bg-gray-900 dark:text-white">
<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 dark:bg-grey-895 dark:border-grey-890 dark:bg-gray-900 dark:text-white">
<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 dark:bg-grey-895 dark:border-grey-890 dark:bg-gray-900 dark:text-white">
<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

@ -0,0 +1,84 @@
<!-- This example requires Tailwind CSS v2.0+ -->
<div class="flex flex-col">
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
<div
class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Name
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Title
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Role
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Edit</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<img
class="h-10 w-10 rounded-full"
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=60"
alt="" />
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
Jane Cooper
</div>
<div class="text-sm text-gray-500">
jane.cooper@example.com
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">
Regional Paradigm Technician
</div>
<div class="text-sm text-gray-500">Optimization</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
Active
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
Admin
</td>
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="#"
class="text-indigo-600 hover:text-indigo-900">Edit</a>
</td>
</tr>
<!-- More rows... -->
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

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

102
src/components/Tags.svelte Normal file
View File

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

View File

@ -0,0 +1,10 @@
<script>
import { _ } from "svelte-i18n";
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('teams')}
</span>
<p class="mb-8 text-lg text-gray-500">everything is more fun together 🏃‍♂️🏃‍♀️🏃‍♂️</p>
</section>

View File

@ -0,0 +1,5 @@
<script>
import { _, locale } from "svelte-i18n";
</script>
<div>$locale $_('hallo')</div>

View File

@ -0,0 +1,189 @@
<script>
import { _, json } from "svelte-i18n";
import Toastify from "toastify-js";
import TracksEmptyState from "./TracksEmptyState.svelte";
import { TrackService } from "@odit/lfk-client-js";
const tracks_promise = TrackService.trackControllerGetAll();
import { getlang } from "./datatable_i18n";
import { Grid, html } from "gridjs";
import "gridjs/dist/theme/mermaid.css";
import { tracks as tracksstore } from "../store.js";
$: trackscache = [];
$: blocked = [];
let table;
let datatable;
let datatable_inited = false;
tracksstore.subscribe((val) => {
trackscache = val;
setTimeout(() => {
if (val.length > 0) {
renderdatatable();
}
}, 100);
});
tracks_promise.then((data) => {
tracksstore.set(data);
});
window.track__edit_cancel = () => renderdatatable();
window.track__edit_save = () => {
const trackid = parseInt(window.event.target.getAttribute("data-trackid"));
if (blocked.includes(trackid)) {
//
} else {
blocked.push(trackid);
const elem = document.querySelector(
`[data-id="triggered_table_actions_${trackid}"]`
).parentNode.parentNode.parentNode;
Toastify({
text: "Track is being updated...",
duration: 500,
}).showToast();
TrackService.trackControllerPut(trackid, {
id: trackid,
name: elem.childNodes[0].childNodes[0].value,
distance: parseInt(elem.childNodes[1].childNodes[0].value),
minimumLapTime: parseInt(elem.childNodes[2].childNodes[0].value),
})
.then((r) => {
Toastify({
text: "Track was updated!",
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
duration: 1000,
}).showToast();
blocked = blocked.filter((e) => e !== trackid);
document
.querySelector(`[data-id="default_table_actions_${trackid}"]`)
.classList.remove("hidden");
document
.querySelector(`[data-id="triggered_table_actions_${trackid}"]`)
.classList.add("hidden");
//
elem.childNodes[0].innerHTML = `<td data-column-id="trackName" class="gridjs-td">${elem.childNodes[0].childNodes[0].value}</td>`;
elem.childNodes[1].innerHTML = `<td data-column-id="trackName" class="gridjs-td">${elem.childNodes[1].childNodes[0].value}</td>`;
elem.childNodes[2].innerHTML = `<td data-column-id="trackName" class="gridjs-td">${elem.childNodes[2].childNodes[0].value}</td>`;
})
.catch((err) => {
console.error(err);
});
}
};
window.track__delete_handler = () => {
const trackid = parseInt(window.event.target.getAttribute("data-trackid"));
document
.querySelector(`[data-id="default_table_actions_${trackid}"]`)
.classList.add("hidden");
document
.querySelector(`[data-id="deleteconfirmation_table_actions_${trackid}"]`)
.classList.remove("hidden");
};
window.track__delete_cancel = () => {
const trackid = parseInt(window.event.target.getAttribute("data-trackid"));
document
.querySelector(`[data-id="default_table_actions_${trackid}"]`)
.classList.remove("hidden");
document
.querySelector(`[data-id="deleteconfirmation_table_actions_${trackid}"]`)
.classList.add("hidden");
};
window.track__delete_confirm = () => {
const trackid = parseInt(window.event.target.getAttribute("data-trackid"));
TrackService.trackControllerRemove(trackid)
.then(() => {
const newStoreVal = trackscache.filter((obj) => obj.id !== trackid);
tracksstore.set(newStoreVal);
renderdatatable();
})
.catch((err) => {
console.log(err);
});
};
window.track__edit_handler = () => {
const trackid = parseInt(window.event.target.getAttribute("data-trackid"));
document
.querySelector(`[data-id="default_table_actions_${trackid}"]`)
.classList.add("hidden");
document
.querySelector(`[data-id="triggered_table_actions_${trackid}"]`)
.classList.remove("hidden");
const elem = document.querySelector(
`[data-id="triggered_table_actions_${trackid}"]`
).parentNode.parentNode.parentNode;
const trackname = elem.childNodes[0].textContent;
const tracklength = parseInt(elem.childNodes[1].textContent);
const trackmintime = parseInt(elem.childNodes[2].textContent);
elem.childNodes[0].innerHTML = `<input type="text" value="${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">`;
elem.childNodes[1].innerHTML = `<input type="text" value="${tracklength}" name="tracklength" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">`;
elem.childNodes[2].innerHTML = `<input type="text" value="${trackmintime}" name="trackmintime" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">`;
};
//
function renderdatatable() {
let tabledata = [];
trackscache.forEach((track) => {
tabledata.push([
track.name,
track.distance,
track.minimumLapTime || 0,
html(`
<div class="hidden" data-id="triggered_table_actions_${track.id}">
<button class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-400 text-base font-medium text-white sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__edit_save()">Save</button>
<button class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-500 text-base font-medium text-white sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__edit_cancel()">Cancel</button>
</div>
<div data-id="default_table_actions_${track.id}">
<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 sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__edit_handler()">Edit</button>
<button class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-500 text-base font-medium text-white sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__delete_handler()">Delete</button>
</div>
<div class="hidden" data-id="deleteconfirmation_table_actions_${track.id}">
<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 sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__delete_cancel()">Cancel</button>
<button class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-500 text-base font-medium text-white sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__delete_confirm()">Confirm</button>
</div>
`),
]);
});
if (datatable_inited === false) {
datatable = new Grid({
columns: [
$_("track-name"),
$_("track-length-in-m"),
$_("minimum-lap-time-in-s"),
$_("action"),
],
language: getlang($json("datatable")),
sort: true,
search: { enabled: true },
data: tabledata,
pagination: {
enabled: true,
limit: 25,
summary: false,
},
}).render(table);
datatable_inited = true;
} else {
datatable.updateConfig({ data: tabledata }).forceRender();
}
}
</script>
{#if trackscache.length > 0}
<div bind:this={table} />
{/if}
{#await tracks_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">{$_('track-data-is-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if trackscache.length === 0}
<TracksEmptyState />
{/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}

View File

@ -9,16 +9,14 @@
class="mx-auto"
style="height:15rem;"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 997 808"
><defs>
viewBox="0 0 997 808"><defs>
<linearGradient
id="a"
x1="570.56"
y1="454"
x2="570.56"
y2="63.07"
gradientUnits="userSpaceOnUse"
>
gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="gray" stop-opacity=".25" />
<stop offset=".54" stop-color="gray" stop-opacity=".12" />
<stop offset="1" stop-color="gray" stop-opacity=".1" />
@ -27,305 +25,235 @@
<path
d="M260.64.9c93 12.73 168.51 101.25 252.3 156.49 18.67 12.31 38.24 23.15 58.93 26.36 68.54 10.64 131.74-63.09 200.6-57.07 55.81 4.88 103.14 64 123.22 133.54s17 147.32 5.85 220.32c-17.46 114-56.14 226.15-123.47 300.3-11.11 12.24-23.52 23.69-37.79 26.47-23.78 4.69-45.83-15.46-66.41-32.12-92.88-75.22-210.3-94.32-314-51.08-35.12 14.64-68.71 36-104.65 46.54-69.21 20.26-147.53-5.34-194.52-76s-54.63-186.8-9.86-260c18.69-30.55 44.45-52.12 64.07-81.6 49.3-74 31.1-148.82 25-235.6C133.9 31.94 206.31-6.53 260.64.9z"
fill="#4299e1"
opacity=".1"
/>
opacity=".1" />
<g opacity=".2">
<path
d="M683.38 545s27.51 27.51 12 68.33S722 722.03 722 722.03l-1.29-.21c-56.5-10-84.57-74.59-53.15-122.59 11.66-17.81 21.25-38.75 15.82-54.23z"
fill="#4299e1"
/>
fill="#4299e1" />
<path
d="M683.38 545.5s27.51 27.51 12 68.33S722 722.53 722 722.53l-1.29-.21c-56.5-10-84.57-74.59-53.15-122.59 11.66-17.86 21.25-38.79 15.82-54.23z"
opacity=".1"
/>
opacity=".1" />
<path
d="M683.38 545s15.53 33.72 0 61.23-2.66 104.71 38.6 115.8"
fill="none"
stroke="#535461"
stroke-miterlimit="10"
/>
stroke-miterlimit="10" />
<path
d="M634.39 656.3s31.39-5 34.25 23.13 60.36 32 60.36 32l-1.08.72c-47.48 31.17-92.86 19.85-86-21.53 2.53-15.35 2.32-30.81-7.53-34.32z"
fill="#4299e1"
/>
fill="#4299e1" />
<path
d="M634.39 656.3s24.37 5.35 22.36 27.5 36.18 48.71 72.25 27.66"
fill="none"
stroke="#535461"
stroke-miterlimit="10"
/>
stroke-miterlimit="10" />
</g>
<g opacity=".2">
<path
d="M357.1 501.68s22.86 22.86 10 56.77 22.12 90.32 22.12 90.32l-1.07-.18c-46.94-8.29-70.27-62-44.17-101.86 9.66-14.83 17.63-32.22 13.12-45.05z"
fill="#4299e1"
/>
fill="#4299e1" />
<path
d="M357.1 502.06s22.86 22.86 10 56.77 22.12 90.32 22.12 90.32l-1.12-.15c-46.94-8.29-70.27-62-44.17-101.86 9.71-14.86 17.68-32.25 13.17-45.08z"
opacity=".1"
/>
opacity=".1" />
<path
d="M357.1 501.68s12.9 28 0 50.87-2.21 87 32.07 96.22"
fill="none"
stroke="#535461"
stroke-miterlimit="10"
/>
stroke-miterlimit="10" />
<path
d="M316.39 594.13s26.11-4.13 28.46 19.22S395 640 395 640l-.9.6c-39.45 25.9-77.16 16.5-71.47-17.89 2.11-12.83 1.94-25.71-6.24-28.58z"
fill="#4299e1"
/>
fill="#4299e1" />
<path
d="M316.39 594.13s20.24 4.44 18.58 22.85 30.06 40.48 60 23"
fill="none"
stroke="#535461"
stroke-miterlimit="10"
/>
stroke-miterlimit="10" />
</g>
<g opacity=".2">
<path
d="M261.1 357.68s22.86 22.86 10 56.77 22.12 90.32 22.12 90.32l-1.07-.18c-46.94-8.29-70.27-62-44.17-101.86 9.66-14.83 17.63-32.22 13.12-45.05z"
fill="#4299e1"
/>
fill="#4299e1" />
<path
d="M261.1 358.06s22.86 22.86 10 56.77 22.12 90.32 22.12 90.32l-1.12-.15c-46.94-8.29-70.27-62-44.17-101.86 9.71-14.86 17.68-32.25 13.17-45.08z"
opacity=".1"
/>
opacity=".1" />
<path
d="M261.1 357.68s12.9 28 0 50.87-2.21 87 32.07 96.22"
fill="none"
stroke="#535461"
stroke-miterlimit="10"
/>
stroke-miterlimit="10" />
<path
d="M220.39 450.13s26.11-4.13 28.46 19.22S299 496 299 496l-.9.6c-39.45 25.9-77.16 16.5-71.47-17.89 2.11-12.83 1.94-25.71-6.24-28.58z"
fill="#4299e1"
/>
fill="#4299e1" />
<path
d="M220.39 450.13s20.24 4.44 18.58 22.85 30.06 40.48 60 23"
fill="none"
stroke="#535461"
stroke-miterlimit="10"
/>
stroke-miterlimit="10" />
</g>
<path
fill="#dfe5ee"
d="M14.76 592.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z"
/>
d="M14.76 592.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
opacity=".05"
d="M14.76 592.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z"
/>
d="M14.76 592.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
fill="#dfe5ee"
d="M14.76 578.75l279.29-91.96 48.82 7.95-277.02 98.77-51.09-14.76z"
/>
d="M14.76 578.75l279.29-91.96 48.82 7.95-277.02 98.77-51.09-14.76z" />
<path
fill="#dfe5ee"
d="M57.9 370.99h18.17v211.17H57.9zM275.88 322.17h18.17v185.06h-18.17z"
/>
d="M57.9 370.99h18.17v211.17H57.9zM275.88 322.17h18.17v185.06h-18.17z" />
<path
fill="#dfe5ee"
d="M339.46 264.27v56.77L112.67 371.7l-26.83 6-66.54 14.86L0 386.88v-65.84l102.91-19.62 25.61-4.88 187.1-35.67 23.84 3.4z"
/>
d="M339.46 264.27v56.77L112.67 371.7l-26.83 6-66.54 14.86L0 386.88v-65.84l102.91-19.62 25.61-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M339.46 264.27v56.77L112.67 371.7l-26.83 6-66.54 14.86L0 386.88v-65.84l102.91-19.62 25.61-4.88 187.1-35.67 23.84 3.4z"
/>
d="M339.46 264.27v56.77L112.67 371.7l-26.83 6-66.54 14.86L0 386.88v-65.84l102.91-19.62 25.61-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M76.07 370.99v11.16l-18.17 4.06v-15.22h18.17zM294.05 322.17v11.29l-18.17 4.05v-15.34h18.17z"
/>
d="M76.07 370.99v11.16l-18.17 4.06v-15.22h18.17zM294.05 322.17v11.29l-18.17 4.05v-15.34h18.17z" />
<path
fill="#dfe5ee"
d="M339.46 264.27v56.77l-92.08 20.57-26.78 5.98-107.93 24.11-26.83 6-66.54 14.86 1.14-64.71 92.47-18.43 25.87-5.15h.01L244.5 283.2l25.82-5.15 69.14-13.78z"
/>
d="M339.46 264.27v56.77l-92.08 20.57-26.78 5.98-107.93 24.11-26.83 6-66.54 14.86 1.14-64.71 92.47-18.43 25.87-5.15h.01L244.5 283.2l25.82-5.15 69.14-13.78z" />
<path
fill="#4299e1"
d="M138.79 304.27l-26.12 67.43-26.83 6 27.07-68.28-10-8 25.61-4.88 10.26 7.73h.01zM271.62 279.03l-24.24 62.58-26.78 5.98 25.14-63.41-1.24-.98-8.76-7.02 25.61-4.88 8.97 6.75 1.29.98h.01z"
/>
d="M138.79 304.27l-26.12 67.43-26.83 6 27.07-68.28-10-8 25.61-4.88 10.26 7.73h.01zM271.62 279.03l-24.24 62.58-26.78 5.98 25.14-63.41-1.24-.98-8.76-7.02 25.61-4.88 8.97 6.75 1.29.98h.01z" />
<path
opacity=".05"
d="M61.88 578.47l18.16-4.26v4.26l-18.16 4.26v-4.26zM278.72 503.54l18.16-4.26v4.26l-18.16 4.26v-4.26z"
/>
d="M61.88 578.47l18.16-4.26v4.26l-18.16 4.26v-4.26zM278.72 503.54l18.16-4.26v4.26l-18.16 4.26v-4.26z" />
<path
d="M761.23 394.51c-28.95 15.25-58.92-2-58.92-2a19.07 19.07 0 01-1.61 2.52c-.37-.28-.75-.57-1.13-.88-12.44-10.17-26.21-34.18-26.21-34.18l-26.83-50.31a78.52 78.52 0 01-7.37-19.19c-4.42-19.18-18.53-20-18.53-20H604.1c-8 0-19.56 1.68-31 4a2.1 2.1 0 00.57-1.14l-.55.11 6.14-6s17.57-7.12 19.64-22.37l9.3-9.15s.3-.8.72-2.21l3.09-3.89c13.46-16.85 14.43-17.13 14.43-17.13l40.81-24.15s14.65-2.08 8.54-43.05-2-46-2-46 11.1-41.05-10.18-35.94-11.66 40.2-11.66 40.2l1.48 7.75a74.2 74.2 0 01-.3 29.35c-2.21 10.45-4.68 23.36-4.06 26.44 1.09 5.37-11.2 12.17-11.2 12.17l-6.95 4.06a35.17 35.17 0 01-24.39 4.16l-.37-.56L602 184a12.05 12.05 0 00-.3 1.3L600 184v.1c0-.36 0-.7.08-1.08a53.88 53.88 0 012.69-11.28c.11-.34.25-.7.37-1A30.72 30.72 0 00632 140.29a30 30 0 00-1.52-9.44 6.51 6.51 0 002.38 0c3.74-.6 7.71-2.52 9.65-5.83a10.7 10.7 0 00-.79-11.72 11.71 11.71 0 00-1.6-1.67c-3.55-4-9.49-5-14.21-7.69-4.49-2.53-7.9-6.57-12.08-9.58-7.9-5.71-18-7.43-27.64-9-4.14-.67-8.35-1.34-12.51-.79-5.26.7-10.1 3.33-14.22 6.62a12.8 12.8 0 00-3.59 3.95c-1.53 3-.86 6.52-1.13 9.84-.66 8.08-7 14.66-14 18.95-4.3 2.63-9 4.73-12.61 8.26s-5.74 9.1-3.49 13.56c1.19 2.36 3.54 4.33 3.56 7a7.84 7.84 0 01-1 3.34 48.67 48.67 0 01-9.59 13.5c-14.6-3.52-30.36 4.22-30.36 4.22l-47.55 2s-13.44-6.1-31 31.52-23.78 39.66-23.78 39.66-32 28.47-11.37 35.59 32-27.46 32-27.46L417 233.84s25.84-24.41 27.91-29.49 14.47-5.08 14.47-5.08l50.41 2a9.59 9.59 0 01.12 1.23c.12 5.64-5.17 10-10.49 12.09-3.36 1.34-7.2 2.35-9.25 5.29s-1.56 7.21.5 10.21a13.42 13.42 0 002.19 2.43 20.53 20.53 0 007.72 5 35.81 35.81 0 007.51 2.08 76 76 0 01-4.25 12.6l2 .86-.66 1.76c-2.82 7.49-8.63 22.27-13.77 31.21-.05 0-9.34 7.88-16.55 15-5.51 5.42-18.79 57.6 20.07 58.54l8.87 6.54h.18c7.89 11.14 33.94 29.44 33.94 29.44l-46.07 2.72a57.66 57.66 0 00-6.85.83q-1.13.2-2.26.45c-8.12 1.78-17.11 1.15-21.93.58-3.19-6-18-3.56-18-3.56s-2.07 30.51 1 47.79 14.47 6.1 14.47 6.1c-2.07-15.25 15-26.47 15-26.47 2.17-1.09 4.74-2.41 7.76-4 9.58-5.14 55.15 2.72 69.24 5.32a47.65 47.65 0 009.41.8c40.39-.66 29.9-20.36 29.9-20.36l-8.13-16a82.22 82.22 0 01-3.56-8.18c-7.18-19-18.34-29.46-26.89-35.06.46-.58.92-1.16 1.37-1.77 0 0-.68-.16-1.71-.47 7.22-3.69 12-6.64 12-6.64s47.55-28.47 45.49-21.35 10.34 34.57 33.08 53.89c.66.56 1.3 1.13 1.94 1.71l.62.57 1.27 1.2.69.67 1.18 1.18.66.68 1.25 1.34.5.54q.85.94 1.68 1.9l.36.43q.64.76 1.27 1.52l.57.71 1 1.28.58.75 1 1.31.52.69 1.16 1.61.28.38q.71 1 1.39 2l.34.51 1 1.51.42.65.89 1.4.39.62.93 1.53.27.44q1.21 2 2.3 4l.12.22.95 1.72.15.28.91 1.72.06.12a151.46 151.46 0 016.87 15.07 26.4 26.4 0 01-3.53.6c-6.2 27.46 10.34 27.46 10.34 27.46s61-9.15 83.74-32.54-4.08-21.44-4.08-21.44z"
transform="translate(-101.5 -46)"
fill="url(#a)"
/>
fill="url(#a)" />
<path
d="M438.5 296.5s25.14 6.52 37.68 40.22a81.56 81.56 0 003.45 8l7.87 15.78s10.15 19.37-28.92 20a45.36 45.36 0 01-9.1-.79c-13.63-2.55-57.71-10.28-67-5.23a172.92 172.92 0 01-16 8l-11-12 4-16s13.21 2.48 24.62-.07a55.86 55.86 0 018.81-1.26l44.59-2.65s-35-25-35-34 36-20 36-20z"
fill="#f8bdc5"
/>
fill="#f8bdc5" />
<path
d="M363.5 357.5s-11 17.94 11.49 21c0 0-16.49 11-14.49 26 0 0-11 11-14-6s-1-47-1-47 18-3 18 6z"
opacity=".05"
/>
opacity=".05" />
<path
d="M438.5 296.5s25.14 6.52 37.68 40.22a81.56 81.56 0 003.45 8l7.87 15.78s10.15 19.37-28.92 20a45.36 45.36 0 01-9.1-.79c-13.63-2.55-57.71-10.28-67-5.23a172.92 172.92 0 01-16 8l-11-12 4-16s13.21 2.48 24.62-.07a55.86 55.86 0 018.81-1.26l44.59-2.65s-35-25-35-34 36-20 36-20z"
opacity=".05"
/>
opacity=".05" />
<path
d="M360.5 372.5l4-16s13.21 2.48 24.62-.07a55.86 55.86 0 018.81-1.26l44.57-2.67s-35-25-35-34 36-20 36-20l.27.08a32.74 32.74 0 00-5.27-2.08s-36 11-36 20 35 34 35 34l-44.56 2.67a55.86 55.86 0 00-8.81 1.26c-11.41 2.55-24.62.07-24.62.07l-4 16 11 12s.74-.3 2.26-1z"
opacity=".05"
/>
opacity=".05" />
<path
d="M363.5 357.5s-11 17.94 11.49 21c0 0-16.49 11-14.49 26 0 0-11 11-14-6s-1-47-1-47 18-3 18 6z"
fill="#ffd97b"
/>
fill="#ffd97b" />
<path
fill="#dfe5ee"
d="M333.76 647.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z"
/>
d="M333.76 647.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
opacity=".05"
d="M333.76 647.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z"
/>
d="M333.76 647.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
fill="#dfe5ee"
d="M333.76 633.75l279.29-91.96 48.82 7.95-277.02 98.77-51.09-14.76z"
/>
d="M333.76 633.75l279.29-91.96 48.82 7.95-277.02 98.77-51.09-14.76z" />
<path
fill="#dfe5ee"
d="M376.9 425.99h18.17v211.17H376.9zM594.88 377.17h18.17v185.06h-18.17z"
/>
d="M376.9 425.99h18.17v211.17H376.9zM594.88 377.17h18.17v185.06h-18.17z" />
<path
fill="#dfe5ee"
d="M658.46 319.27v56.77L431.67 426.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z"
/>
d="M658.46 319.27v56.77L431.67 426.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M658.46 319.27v56.77L431.67 426.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z"
/>
d="M658.46 319.27v56.77L431.67 426.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M395.07 425.99v11.16l-18.17 4.06v-15.22h18.17zM613.05 377.17v11.29l-18.17 4.05v-15.34h18.17z"
/>
d="M395.07 425.99v11.16l-18.17 4.06v-15.22h18.17zM613.05 377.17v11.29l-18.17 4.05v-15.34h18.17z" />
<path
fill="#dfe5ee"
d="M658.46 319.27v56.77l-92.08 20.57-26.78 5.98-107.93 24.11-26.83 6-66.54 14.86 1.14-64.71 92.47-18.43 25.87-5.15h.01L563.5 338.2l25.82-5.15 69.14-13.78z"
/>
d="M658.46 319.27v56.77l-92.08 20.57-26.78 5.98-107.93 24.11-26.83 6-66.54 14.86 1.14-64.71 92.47-18.43 25.87-5.15h.01L563.5 338.2l25.82-5.15 69.14-13.78z" />
<path
fill="#4299e1"
d="M457.79 359.27l-26.12 67.43-26.83 6 27.07-68.28-10.01-8 25.62-4.88 10.26 7.73h.01zM590.62 334.03l-24.24 62.58-26.78 5.98 25.14-63.41-1.24-.98-8.76-7.02 25.61-4.88 8.97 6.75 1.29.98h.01z"
/>
d="M457.79 359.27l-26.12 67.43-26.83 6 27.07-68.28-10.01-8 25.62-4.88 10.26 7.73h.01zM590.62 334.03l-24.24 62.58-26.78 5.98 25.14-63.41-1.24-.98-8.76-7.02 25.61-4.88 8.97 6.75 1.29.98h.01z" />
<path
opacity=".05"
d="M380.88 633.47l18.16-4.26v4.26l-18.16 4.26v-4.26zM597.72 558.54l18.16-4.26v4.26l-18.16 4.25v-4.25z"
/>
d="M380.88 633.47l18.16-4.26v4.26l-18.16 4.26v-4.26zM597.72 558.54l18.16-4.26v4.26l-18.16 4.25v-4.25z" />
<path
d="M562.63 146.9l-39.48 23.75s-.94.28-14 16.85l-4.69 6c-9.92 12.75-30.54 17-30.54 17l-13.08-20.12-2.76-24.44 2.9-9.87 2.3-7.82 20.12 2.88 8.1 4.37 10-10c.78.21 1.57.4 2.36.55a33.55 33.55 0 0023.59-4.05l6.72-4s11.88-6.69 10.83-12c-.6-3 1.79-15.72 3.93-26a74.16 74.16 0 00.29-28.86l-1.43-7.62S538.5 29 559.07 24s9.85 35.34 9.85 35.34-3.94 4.94 2 45.23-8.29 42.33-8.29 42.33z"
fill="#f8bdc5"
/>
fill="#f8bdc5" />
<path
d="M497.55 141.54c-1.12 10.58 3.95 12 3.95 12l3 12v13l-9 15h-22l-19-9v-42l-3-10a38.7 38.7 0 005-.65c9.65-1.93 15.12-7.29 18.2-13.11 4.8-9.11 3.8-19.28 3.8-19.28s40-11 26 20c-1.86 4.13-3.28 7.76-4.35 11a53.72 53.72 0 00-2.6 11.04zM472.09 229.33a2.08 2.08 0 01-.59 1.17 8.65 8.65 0 00-1.29 2c-4.68 8.83-13.71 40-13.71 40l-49-18-.71-.18-19.29-4.82c5.47-3.91 14.61-27.36 18.33-37.42 1-2.81 1.67-4.58 1.67-4.58l52 5s13.48 11.8 12.59 16.83z"
fill="#f8bdc5"
/>
fill="#f8bdc5" />
<path
d="M451.5 129.5s-15 6-30 0-33 3-33 3l-46 2s-13-6-30 31-23 39-23 39-31 28-11 35 31-27 31-27l11-21s25-24 27-29 14-5 14-5l50 2-.31 14.62 15.31 13.38 14-12 11-22z"
fill="#f8bdc5"
/>
fill="#f8bdc5" />
<path
d="M504.5 119.5c-1.86 4.13-3.28 7.76-4.35 11h-1.65a29.92 29.92 0 01-23.81-11.76c4.81-9.07 3.81-19.24 3.81-19.24s40-11 26 20z"
opacity=".1"
/>
opacity=".1" />
<circle cx="498.5" cy="99.5" r="30" fill="#f8bdc5" />
<path
d="M472.09 229.33a2.08 2.08 0 01-.59 1.17 8.65 8.65 0 00-1.29 2c-17.79-.78-56.72-17.11-64.38-20.38 1-2.81 1.67-4.58 1.67-4.58l52 5s13.48 11.76 12.59 16.79zM509.19 187.5l-4.69 6c-9.92 12.75-30.54 17-30.54 17l-13.08-20.12-2.76-24.44L459 142c2.79 13 10.59 40.12 22.5 42.5 19.91 4 18.48-24.53 17.5-36.5a16.2 16.2 0 01.5-5.5l4 3 .36.55c9.53 14.8 7.39 32.42 5.33 41.45z"
opacity=".1"
/>
opacity=".1" />
<path
d="M456.5 127.5h-10s10 64-37 46c0 0 4 15-5 36 0 0 48 21 67 21l6-6s17-7 19-22l9-9s10-27-4-48l-4-3a16.16 16.16 0 00-.5 5.5c1 12 2.41 40.48-17.5 36.5-25-5-23-57-23-57z"
fill="#eb6576"
/>
fill="#eb6576" />
<path
d="M388.5 309.5l16 12s26 8 47-21c0 0-13-3-11-10s-52 19-52 19z"
fill="#5e52ad"
/>
fill="#5e52ad" />
<path
d="M388.5 309.5l16 12s26 8 47-21c0 0-13-3-11-10s-52 19-52 19z"
opacity=".05"
/>
opacity=".05" />
<path
d="M472.09 230.33a2.08 2.08 0 01-.59 1.17c-4 4-15 42-15 42l-49-18-.71-.18a70.21 70.21 0 0112-6.06c3.41-1.32 6.84-2.72 9.81-4.06a44.92 44.92 0 006.91-3.7c4.19-3 20.28-7.8 36.58-11.17z"
opacity=".1"
/>
opacity=".1" />
<path
d="M585.5 372.5l-15 4s-.34-1-1-2.74c-3.2-8.14-13.86-32.61-32-48.26-22-19-34-46-32-53s-44 21-44 21-33.35 20.75-55.39 20.78a23.28 23.28 0 01-9.61-1.78c-8.5-3.83-11.94-10.54-13.09-17a29.72 29.72 0 016-23.31 68.38 68.38 0 0129.4-21.93c3.41-1.32 6.84-2.72 9.81-4.06a44.92 44.92 0 006.91-3.7c7-5 47-15 66-15h16s13.64.8 17.92 19.66a78 78 0 007.1 18.84l25.95 49.5s13.32 23.61 25.35 33.61c3.43 2.86 6.76 4.61 9.65 4.39 13-1-18 19-18 19z"
fill="#f8bdc5"
/>
fill="#f8bdc5" />
<path
d="M538.66 84.52A10.67 10.67 0 00537.9 73c-3.36-4.37-9.43-5.37-14.22-8.11-4.35-2.49-7.64-6.46-11.68-9.42-7.64-5.61-17.38-7.31-26.73-8.84-4-.66-8.08-1.32-12.1-.77-5.09.69-9.77 3.28-13.76 6.51a12.52 12.52 0 00-3.47 3.88c-1.48 2.92-.83 6.42-1.09 9.68-.64 7.95-6.78 14.42-13.55 18.63-4.16 2.59-8.74 4.65-12.2 8.12s-5.55 8.95-3.37 13.33c1.16 2.32 3.43 4.26 3.44 6.86a7.8 7.8 0 01-.94 3.29 47.87 47.87 0 01-9.54 13.55c-3.54 3.52-7.77 6.68-9.61 11.33-1.77 4.47-1 9.57.63 14.1.76 2.16 1.71 4.32 1.75 6.6.11 5.55-5 9.81-10.15 11.89-3.25 1.31-7 2.31-8.95 5.2s-1.51 7.09.48 10 5.2 4.86 8.5 6.19a31.16 31.16 0 0019.38 1.58 23.74 23.74 0 0015.15-11.78c3-5.84 3.2-13 7.36-18.1 7.33-8.91 22.79-6.66 30.44-15.31 6.14-6.95 4.47-17.48 4-26.74a82.37 82.37 0 01.64-15.45c.5-3.64 1.42-7.55 4.28-9.85 2.31-1.86 5.43-2.28 8.37-2.64 6.14-.77 13.21-2.26 16-7.79 1.16-2.3 1.51-5.2 3.6-6.7 2.89-2.08 6.94 0 9.46 2.5 2.78 2.77 5.11 6.18 9.34 5.49 3.59-.56 7.43-2.45 9.3-5.71z"
opacity=".1"
/>
opacity=".1" />
<path
d="M537.58 83.44a10.67 10.67 0 00-.77-11.52c-3.36-4.37-9.43-5.37-14.22-8.11-4.35-2.49-7.64-6.46-11.68-9.42-7.64-5.61-17.38-7.31-26.73-8.84-4-.66-8.08-1.32-12.1-.77-5.09.69-9.77 3.28-13.76 6.51a12.52 12.52 0 00-3.47 3.88c-1.48 2.92-.83 6.42-1.09 9.68-.64 7.95-6.78 14.42-13.55 18.63-4.16 2.59-8.74 4.65-12.2 8.12s-5.55 8.95-3.37 13.33c1.16 2.32 3.43 4.26 3.44 6.86a7.8 7.8 0 01-.94 3.29 47.87 47.87 0 01-9.54 13.55c-3.54 3.52-7.77 6.68-9.61 11.33-1.77 4.47-1 9.57.63 14.1.76 2.16 1.71 4.32 1.75 6.6.11 5.55-5 9.81-10.15 11.89-3.25 1.31-7 2.31-8.95 5.2s-1.51 7.09.48 10 5.2 4.86 8.5 6.19a31.16 31.16 0 0019.38 1.58 23.74 23.74 0 0015.15-11.78c3-5.84 3.2-13 7.36-18.1 7.33-8.91 22.79-6.66 30.44-15.31 6.14-6.95 4.47-17.48 4-26.74a82.37 82.37 0 01.64-15.45c.5-3.64 1.42-7.55 4.28-9.85 2.31-1.86 5.43-2.28 8.37-2.64 6.14-.77 13.21-2.26 16-7.79 1.16-2.3 1.51-5.2 3.6-6.7 2.89-2.08 6.94 0 9.46 2.5 2.78 2.77 5.11 6.18 9.34 5.49 3.59-.57 7.44-2.46 9.31-5.71z"
fill="#6e464e"
/>
fill="#6e464e" />
<path d="M434 202s11 9 25 8" opacity=".1" />
<path
d="M605.92 353.68c-5.74 5.64-18.42 13.82-18.42 13.82l-15 4s-11-32-33-51-34-46-32-53-44 21-44 21-33.35 20.75-55.39 20.78a23.28 23.28 0 01-9.61-1.78c-8.5-3.83-11.94-10.54-13.09-17a28.68 28.68 0 01.82-13.31 29.19 29.19 0 00-2.82 18.31c1.15 6.46 4.59 13.17 13.09 17a23.28 23.28 0 009.61 1.78c22 0 55.39-20.78 55.39-20.78s46-28 44-21 10 34 32 53 33 51 33 51l15-4s25.89-16.71 20.42-18.82z"
opacity=".05"
/>
opacity=".05" />
<path
d="M429.5 246.5l-1.7.79a61.63 61.63 0 00-28.9 27.46c-13.4 26.08 4.31 37.83 7.21 39.53a23.28 23.28 0 01-9.61-1.78c-8.5-3.83-11.94-10.54-13.09-17a29.72 29.72 0 016-23.31 68.38 68.38 0 0129.4-21.93c3.41-1.32 6.84-2.72 9.81-4.06z"
opacity=".1"
/>
opacity=".1" />
<path
d="M427.5 246.5s-21-7.46-35-3.73c0 0-9 7.73-16 14.73-5.7 5.7-20 64 28 57 0 0-22.16-11.42-7.6-39.75a61.62 61.62 0 0128.9-27.46z"
fill="#5e52ad"
/>
fill="#5e52ad" />
<path
d="M585.5 372.5l-15 4s-.34-1-1-2.74c3.49-.92 8-3 8-7.26 0 0 11.45-10.69 16.35-17.39 3.43 2.86 6.76 4.61 9.65 4.39 13-1-18 19-18 19z"
opacity=".1"
/>
opacity=".1" />
<path
d="M578.5 367.5s15-14 18-20c0 0 29 17 57 2 0 0 26-2 4 21s-81 32-81 32-16 0-10-27c0 0 12-1 12-8z"
fill="#ffd97b"
/>
fill="#ffd97b" />
<path
fill="#dfe5ee"
d="M667.76 717.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z"
/>
d="M667.76 717.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
opacity=".05"
d="M667.76 717.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z"
/>
d="M667.76 717.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
fill="#dfe5ee"
d="M667.76 703.75l279.29-91.96 48.82 7.95-277.02 98.77-51.09-14.76z"
/>
d="M667.76 703.75l279.29-91.96 48.82 7.95-277.02 98.77-51.09-14.76z" />
<path
fill="#dfe5ee"
d="M710.9 495.99h18.17v211.17H710.9zM928.88 447.17h18.17v185.06h-18.17z"
/>
d="M710.9 495.99h18.17v211.17H710.9zM928.88 447.17h18.17v185.06h-18.17z" />
<path
fill="#dfe5ee"
d="M992.46 389.27v56.77L765.67 496.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z"
/>
d="M992.46 389.27v56.77L765.67 496.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M992.46 389.27v56.77L765.67 496.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z"
/>
d="M992.46 389.27v56.77L765.67 496.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M729.07 495.99v11.16l-18.17 4.06v-15.22h18.17zM947.05 447.17v11.29l-18.17 4.05v-15.34h18.17z"
/>
d="M729.07 495.99v11.16l-18.17 4.06v-15.22h18.17zM947.05 447.17v11.29l-18.17 4.05v-15.34h18.17z" />
<path
fill="#dfe5ee"
d="M992.46 389.27v56.77l-92.08 20.57-26.78 5.98-107.93 24.11-26.83 6-66.54 14.86 1.14-64.71 92.47-18.43 25.87-5.15h.01L897.5 408.2l25.82-5.15 69.14-13.78z"
/>
d="M992.46 389.27v56.77l-92.08 20.57-26.78 5.98-107.93 24.11-26.83 6-66.54 14.86 1.14-64.71 92.47-18.43 25.87-5.15h.01L897.5 408.2l25.82-5.15 69.14-13.78z" />
<path
fill="#4299e1"
d="M791.79 429.27l-26.12 67.43-26.83 6 27.07-68.28-10.01-8 25.62-4.88 10.26 7.73h.01zM924.62 404.03l-24.24 62.58-26.78 5.98 25.14-63.41-1.24-.98-8.76-7.02 25.61-4.88 8.97 6.75 1.29.98h.01z"
/>
d="M791.79 429.27l-26.12 67.43-26.83 6 27.07-68.28-10.01-8 25.62-4.88 10.26 7.73h.01zM924.62 404.03l-24.24 62.58-26.78 5.98 25.14-63.41-1.24-.98-8.76-7.02 25.61-4.88 8.97 6.75 1.29.98h.01z" />
<path
opacity=".05"
d="M714.88 703.47l18.16-4.26v4.26l-18.16 4.26v-4.26zM931.72 628.54l18.16-4.26v4.26l-18.16 4.25v-4.25z"
/></svg
>
d="M714.88 703.47l18.16-4.26v4.26l-18.16 4.26v-4.26zM931.72 628.54l18.16-4.26v4.26l-18.16 4.25v-4.25z" /></svg>
<p class="mb-16 text-lg text-gray-500">
<span class="font-bold">{$_("no-tracks-added-yet")}</span><br />
<span>{$_("add-your-first-track")}</span>
<span class="font-bold">{$_('no-tracks-added-yet')}</span><br />
<span>{$_('add-your-first-track')}</span>
</p>
</div>

View File

@ -0,0 +1,25 @@
<script>
import { _ } from "svelte-i18n";
import AddTrackModal from "./AddTrackModal.svelte";
let modal_open = false;
import Tracks from "./Tracks.svelte";
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
Tracks
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
Create Track
</button>
</span>
<p class="mb-8 text-lg text-gray-500">
configure the tracks & minimum lap times
</p>
<Tracks />
</section>
<AddTrackModal bind:modal_open />

View File

@ -0,0 +1,128 @@
<script>
import { _ } from "svelte-i18n";
import { UserService } from "@odit/lfk-client-js";
import "gridjs/dist/theme/mermaid.css";
import { tracks as tracksstore } from "../store.js";
import PromiseError from "./PromiseError.svelte";
export let params;
const user_promise = UserService.userControllerGetOne(params.userid);
user_promise.then((data) => {
console.log(data);
tracksstore.set(data);
});
</script>
{#await user_promise}
<!-- -->
{:then user}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
class="flex-shrink-0 w-5 h-5 mr-2"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">{$_('users')}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">{user.firstname}
{user.middlename || ''}
{user.lastname}</span>
</li>
</ol>
</nav>
</div>
</div>
<span
class="mb-4 text-3xl font-extrabold leading-tight">{user.firstname}
{user.middlename || ''}
{user.lastname}
<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">Delete
User</button></span>
<!-- -->
<div class="mt-2 flex items-center">
<img
alt={$_('profile-picture')}
class="inline-block h-20 w-20 rounded-full overflow-hidden bg-gray-100"
src={user.profilePic} />
<!-- <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 class="mt-3 text-sm w-full">
<input
id="enabled"
name="enabled"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
<label
for="enabled"
class="ml-1 font-medium text-gray-700">Active?</label>
<p class="text-gray-500">set the user active/ inactive</p>
</div>
<div class="text-sm w-full">
<label for="firstname" class="font-medium text-gray-700">First name</label>
<input
autocomplete="off"
placeholder="First name"
type="text"
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 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label for="middlename" class="font-medium text-gray-700">Middle name</label>
<input
autocomplete="off"
placeholder="Middle name"
type="text"
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 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label for="lastname" class="font-medium text-gray-700">Last name</label>
<input
autocomplete="off"
placeholder="Last name"
type="text"
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 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" />
</div>
</section>
{:catch error}
<!-- promise was rejected -->
<PromiseError {error} />
{/await}

View File

@ -1,30 +1,25 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddUserModal from "./AddUserModal.svelte";
export let modal_open = false;
import UsersOverview from "./UsersOverview.svelte";
let current_users = [];
</script>
<section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("users")}
</h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("USER:CREATE")}
<span class="mb-1 text-3xl font-extrabold leading-tight">
Users
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm"
>
{$_("create-user")}
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 User
</button>
{/if}
<UsersOverview bind:current_users />
</span>
<p class="mb-8 text-lg text-gray-500">
manage admin users
</p>
<UsersOverview />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes("USER:CREATE")}
<AddUserModal bind:current_users bind:modal_open />
{/if}
<AddUserModal bind:modal_open />

View File

@ -1,15 +1,13 @@
<script>
import { _ } from "svelte-i18n";
import AddUserModal from "./AddUserModal.svelte";
import users_empty from "./users_empty.svg";
let modal_open = false;
</script>
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={users_empty} alt="" />
<span class="font-bold">{$_("there-are-no-users-added-yet")}</span><br />
<span>{$_("add-your-first-user")}</span>
<span class="font-bold">There are no users added yet.</span><br />
<span>Add your first user</span>
</p>
</div>

View File

@ -0,0 +1,217 @@
<script>
import { _, json } from "svelte-i18n";
import Toastify from "toastify-js";
import TracksEmptyState from "./TracksEmptyState.svelte";
import { TrackService, UserService } from "@odit/lfk-client-js";
const users_promise = UserService.userControllerGetAll();
import { getlang } from "./datatable_i18n";
import { Grid, html } from "gridjs";
import "gridjs/dist/theme/mermaid.css";
import { tracks as tracksstore } from "../store.js";
import UsersEmptyState from "./UsersEmptyState.svelte";
$: userscache = [];
$: blocked = [];
let table;
let datatable;
let datatable_inited = false;
tracksstore.subscribe((val) => {
userscache = val;
});
users_promise.then((data) => {
console.log(data);
tracksstore.set(data);
});
</script>
{#await users_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">users are being loaded...</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then users}
{#if userscache.length === 0}
<UsersEmptyState />
{:else}
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Name
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Title
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Groups
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Action</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each users as u}
<tr>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if u.profilePic}
<div class="flex-shrink-0 h-10 w-10">
<img
class="h-10 w-10 rounded-full"
src={u.profilePic}
alt="" />
</div>
{/if}
<div class="ml-4">
<div
class="text-sm font-medium text-gray-900 dark:text-gray-100">
{u.firstname}
{u.middlename || ''}
{u.lastname}
</div>
<div class="text-sm text-gray-500">
{u.email || u.username}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900 dark:text-gray-100">
Regional Paradigm Technician
</div>
<div class="text-sm text-gray-500">Optimization</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if u.enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">Active</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">Inactive</span>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{#each u.groups as g}
<a
href="../groups/{g.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{g.name}</a>
{/each}
</td>
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{u.id}"
class="text-indigo-600 hover:text-indigo-900">Edit</a>
<span
tabindex="0"
href="#"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">Delete</span>
</td>
</tr>
{/each}
</tbody>
</table>
<div
class="grid px-4 py-3 text-xs font-semibold tracking-wide text-gray-500 uppercase border-t dark:border-gray-700 bg-gray-50 sm:grid-cols-9 dark:text-gray-400 dark:bg-gray-900">
<span class="flex items-center col-span-3"> Showing 21-30 of 100 </span>
<span class="col-span-2" />
<!-- Pagination -->
<span class="flex col-span-4 mt-2 sm:mt-auto sm:justify-end">
<nav aria-label="Table navigation">
<ul class="inline-flex items-center">
<li>
<button
class="px-3 py-1 rounded-md rounded-l-lg focus:outline-none focus:shadow-outline-purple"
aria-label="Previous">
<svg
aria-hidden="true"
class="w-4 h-4 fill-current"
viewBox="0 0 20 20">
<path
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"
fill-rule="evenodd" />
</svg>
</button>
</li>
<li>
<button
class="px-3 py-1 rounded-md focus:outline-none focus:shadow-outline-purple">
1
</button>
</li>
<li>
<button
class="px-3 py-1 rounded-md focus:outline-none focus:shadow-outline-purple">
2
</button>
</li>
<li>
<button
class="px-3 py-1 text-white transition-colors duration-150 bg-purple-600 border border-r-0 border-purple-600 rounded-md focus:outline-none focus:shadow-outline-purple">
3
</button>
</li>
<li>
<button
class="px-3 py-1 rounded-md focus:outline-none focus:shadow-outline-purple">
4
</button>
</li>
<li><span class="px-3 py-1">...</span></li>
<li>
<button
class="px-3 py-1 rounded-md focus:outline-none focus:shadow-outline-purple">
8
</button>
</li>
<li>
<button
class="px-3 py-1 rounded-md focus:outline-none focus:shadow-outline-purple">
9
</button>
</li>
<li>
<button
class="px-3 py-1 rounded-md rounded-r-lg focus:outline-none focus:shadow-outline-purple"
aria-label="Next">
<svg
class="w-4 h-4 fill-current"
aria-hidden="true"
viewBox="0 0 20 20">
<path
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"
fill-rule="evenodd" />
</svg>
</button>
</li>
</ul>
</nav>
</span>
</div>
</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}

View File

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

View File

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

View File

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

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

View File

@ -1,323 +0,0 @@
.simplecontent * {
margin: 0;
padding: 0;
}
.simplecontent address,
.simplecontent area,
.simplecontent article,
.simplecontent aside,
.simplecontent audio,
.simplecontent blockquote,
.simplecontent datalist,
.simplecontent details,
.simplecontent dl,
.simplecontent fieldset,
.simplecontent figure,
.simplecontent form,
.simplecontent iframe,
.simplecontent img,
.simplecontent input,
.simplecontent meter,
.simplecontent nav,
.simplecontent ol,
.simplecontent optgroup,
.simplecontent option,
.simplecontent output,
.simplecontent p,
.simplecontent pre,
.simplecontent progress,
.simplecontent ruby,
.simplecontent section,
.simplecontent table,
.simplecontent textarea,
.simplecontent ul,
.simplecontent video {
margin-bottom: 1rem;
}
.simplecontent button,
.simplecontent html,
.simplecontent input,
.simplecontent select {
font-family: var(--nc-font-sans);
}
.simplecontent body {
margin: 0 auto;
max-width: 750px;
padding: 2rem;
border-radius: 6px;
overflow-x: hidden;
word-break: break-word;
overflow-wrap: break-word;
background: var(--nc-bg-1);
color: var(--nc-tx-2);
font-size: 1.03rem;
line-height: 1.5;
}
.simplecontent h1,
.simplecontent h2,
.simplecontent h3,
.simplecontent h4,
.simplecontent h5,
.simplecontent h6 {
line-height: 1;
color: var(--nc-tx-1);
padding-top: 0.875rem;
}
.simplecontent h1,
.simplecontent h2,
.simplecontent h3 {
color: var(--nc-tx-1);
padding-bottom: 2px;
margin-bottom: 8px;
border-bottom: 1px solid var(--nc-bg-2);
}
.simplecontent h4,
.simplecontent h5,
.simplecontent h6 {
margin-bottom: 0.3rem;
}
.simplecontent h1 {
font-size: 2.25rem;
}
.simplecontent h2 {
font-size: 1.85rem;
}
.simplecontent h3 {
font-size: 1.55rem;
}
.simplecontent h4 {
font-size: 1.25rem;
}
.simplecontent h5 {
font-size: 1rem;
}
.simplecontent h6 {
font-size: 0.875rem;
}
.simplecontent a {
color: #3d5af1;
}
.simplecontent a:hover {
color: var(--nc-lk-2);
}
.simplecontent abbr:hover {
cursor: help;
}
.simplecontent blockquote {
padding: 1.5rem;
background: #ddd;
border-left: 5px solid var(--nc-bg-3);
}
.simplecontent abbr {
cursor: help;
}
.simplecontent blockquote :last-child {
padding-bottom: 0;
margin-bottom: 0;
}
.simplecontent header {
background: #ddd;
border-bottom: 1px solid var(--nc-bg-3);
padding: 2rem 1.5rem;
margin: -2rem calc(0px - (50vw - 50%)) 2rem;
padding-left: calc(50vw - 50%);
padding-right: calc(50vw - 50%);
}
.simplecontent header h1,
.simplecontent header h2,
.simplecontent header h3 {
padding-bottom: 0;
border-bottom: 0;
}
.simplecontent header > :first-child {
margin-top: 0;
padding-top: 0;
}
.simplecontent header > :last-child {
margin-bottom: 0;
}
.simplecontent a button,
.simplecontent button,
.simplecontent input[type="button"],
.simplecontent input[type="reset"],
.simplecontent input[type="submit"] {
font-size: 1rem;
display: inline-block;
padding: 6px 12px;
text-align: center;
text-decoration: none;
white-space: nowrap;
background: #3d5af1;
color: var(--nc-lk-tx);
border: 0;
border-radius: 4px;
box-sizing: border-box;
cursor: pointer;
color: var(--nc-lk-tx);
}
.simplecontent a button[disabled],
.simplecontent button[disabled],
.simplecontent input[type="button"][disabled],
.simplecontent input[type="reset"][disabled],
.simplecontent input[type="submit"][disabled] {
cursor: default;
opacity: 0.5;
cursor: not-allowed;
}
.simplecontent .button:focus,
.simplecontent .button:hover,
.simplecontent button:focus,
.simplecontent button:hover,
.simplecontent input[type="button"]:focus,
.simplecontent input[type="button"]:hover,
.simplecontent input[type="reset"]:focus,
.simplecontent input[type="reset"]:hover,
.simplecontent input[type="submit"]:focus,
.simplecontent input[type="submit"]:hover {
background: var(--nc-lk-2);
}
.simplecontent code,
.simplecontent kbd,
.simplecontent pre,
.simplecontent samp {
font-family: var(--nc-font-mono);
}
.simplecontent code,
.simplecontent kbd,
.simplecontent pre,
.simplecontent samp {
background: #ddd;
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
padding: 3px 6px;
font-size: 0.9rem;
}
.simplecontent kbd {
border-bottom: 3px solid var(--nc-bg-3);
}
.simplecontent pre {
padding: 1rem 1.4rem;
max-width: 100%;
overflow: auto;
}
.simplecontent pre code {
background: inherit;
font-size: inherit;
color: inherit;
border: 0;
padding: 0;
margin: 0;
}
.simplecontent code pre {
display: inline;
background: inherit;
font-size: inherit;
color: inherit;
border: 0;
padding: 0;
margin: 0;
}
.simplecontent details {
padding: 0.6rem 1rem;
background: #ddd;
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
}
.simplecontent summary {
cursor: pointer;
font-weight: 700;
}
.simplecontent details[open] {
padding-bottom: 0.75rem;
}
.simplecontent details[open] summary {
margin-bottom: 6px;
}
.simplecontent details[open] > :last-child {
margin-bottom: 0;
}
.simplecontent dt {
font-weight: 700;
}
.simplecontent dd::before {
content: "→ ";
}
.simplecontent hr {
border: 0;
border-bottom: 1px solid var(--nc-bg-3);
margin: 1rem auto;
}
.simplecontent fieldset {
margin-top: 1rem;
padding: 2rem;
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
}
.simplecontent legend {
padding: auto 0.5rem;
}
.simplecontent table {
border-collapse: collapse;
width: 100%;
}
.simplecontent td,
.simplecontent th {
border: 1px solid var(--nc-bg-3);
text-align: left;
padding: 0.5rem;
}
.simplecontent th {
background: #ddd;
}
.simplecontent tr:nth-child(even) {
background: #ddd;
}
.simplecontent table caption {
font-weight: 700;
margin-bottom: 0.5rem;
}
.simplecontent textarea {
max-width: 100%;
}
.simplecontent ol,
.simplecontent ul {
padding-left: 2rem;
}
.simplecontent li {
margin-top: 0.4rem;
}
.simplecontent ol ol,
.simplecontent ol ul,
.simplecontent ul ol,
.simplecontent ul ul {
margin-bottom: 0;
}
.simplecontent mark {
padding: 3px 6px;
background: var(--nc-ac-1);
color: var(--nc-ac-tx);
}
.simplecontent input,
.simplecontent select,
.simplecontent textarea {
padding: 6px 12px;
margin-bottom: 0.5rem;
background: #ddd;
color: var(--nc-tx-2);
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
box-shadow: none;
box-sizing: border-box;
}
.simplecontent img {
max-width: 100%;
}
.simplecontent blockquote {
background: #ececec;
}
.simplecontent ol {
list-style-type: decimal;
}
.simplecontent ul {
list-style-type: circle;
}

View File

@ -1,218 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { RunnerCardService } from "@odit/lfk-client-js";
import { createEventDispatcher } from "svelte";
import toast from "svelte-french-toast";
import DocumentServer from "../pdf_generation/DocumentServer";
export let bulk_modal_open;
const dispatch = createEventDispatcher();
const documentServer = new DocumentServer(config.baseurl_documentserver,config.documentserver_key);
$: card_count = 0;
$: is_card_count_valid = card_count > 0;
$: processed_last_submit = true;
$: createbtnenabled = is_card_count_valid;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
bulk_modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit_with_print();
}
}
};
})();
function submit_without_print() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("creating-blanco-cards"));
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, true)
.then((result) => {
bulk_modal_open = false;
//
toast.dismiss();
toast.success($_("created-blanco-cards"));
dispatch("created", { cards: result });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
}
}
function submit_with_print() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.dismiss();
toast.loading($_("creating-blanco-cards"));
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, true)
.then((result) => {
bulk_modal_open = false;
//
toast.dismiss();
toast.success($_("created-blanco-cards"));
toast.loading($_("generating-pdf"));
dispatch("created", { cards: result });
documentServer.generateCards(result, "de")
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Bulkcards.pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.dismiss();
toast.success($_("pdf-successfully-generated"));
})
.catch((err) => {
console.error(err);
});
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
}
}
</script>
{#if bulk_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
bulk_modal_open = false;
}}
>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full relative z-10"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<div
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="size-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
/></svg
>
</div>
<div class="mt-3 sm:mt-0">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-bulk-blanco-cards")}
</h3>
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"just-enter-how-many-you-want-and-the-system-will-create-them"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="amount"
class="block text-sm font-medium text-gray-700"
>{$_("amount")}</label
>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_card_count_valid}
class:focus:border-red-500={!is_card_count_valid}
class:focus:ring-red-500={!is_card_count_valid}
bind:value={card_count}
type="number"
step="1"
name="amount"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 p-2"
placeholder="400"
/>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
>{$_("cards")}</span
>
</div>
{#if !is_card_count_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("you-must-create-at-least-one-card-or-cancel")}
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit_with_print}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create-and-generate-pdf")}
</button>
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit_without_print}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-400 text-base font-medium text-white hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create-without-pdf")}
</button>
<button
on:click={() => {
bulk_modal_open = false;
}}
type="button"
class="mr-auto w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,123 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_card = {
id: 0,
code: "",
runner: {
firstname: "",
lastname: "",
},
};
const dispatch = createEventDispatcher();
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
async function submit() {
dispatch("delete", { id: delete_card.id });
modal_open = false;
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw] relative z-10"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<div
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="size-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
/></svg
>
</div>
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("please-confirm-the-deletion-of-card")}
</h3>
<div class="w-full">
{$_("card")} #{delete_card.code}<br />
<span class="inline-block">
{$_("runner")}:
{#if delete_card.runner}
<span class="inline-block"
>{delete_card.runner.firstname}
{delete_card.runner.lastname}</span
>
{:else}
{$_("non-blanko")}
{/if}</span
>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
on:click={submit}
type="button"
class="confirm_deletion_button"
>
{$_("delete")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="cancel_modal_button"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

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

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,480 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import {
GroupContactService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail";
import isMobilePhone from "validator/es/lib/isMobilePhone";
import toast from "svelte-french-toast";
export let modal_open;
export let current_contacts;
$: selected_team = [];
let firstname_input;
let lastname_input;
let middlename_input;
let phone_input;
let email_input;
let address_input1;
let address_input2;
let address_zipcode;
let address_city;
let teams = [];
let orgs = [];
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
function focus(el) {
el.focus();
}
$: middlename_input_value = "";
$: phone_input_value = "";
$: email_input_value = "";
$: lastname_input_value = "";
$: firstname_input_value = "";
$: address_input1_value = "";
$: address_input2_value = "";
$: address_zipcode_value = "";
$: address_city_value = "";
$: processed_last_submit = true;
$: address_checked = false;
$: isPhoneValidOrEmpty =
(phone_input_value.includes("+") &&
isMobilePhone(
phone_input_value
.replaceAll("(", "")
.replaceAll(")", "")
.replaceAll("-", "")
.replaceAll(" ", "")
)) ||
phone_input_value === "";
$: isEmailValidOrEmpty =
isEmail(email_input_value) || email_input_value === "";
$: isLastnameValid = lastname_input_value.trim().length !== 0;
$: isFirstnameValid = firstname_input_value.trim().length !== 0;
$: isAddress1Valid = address_input1_value.trim().length !== 0;
$: iszipcodevalid = address_zipcode_value.trim().length !== 0;
$: iscityvalid = address_city_value.trim().length !== 0;
$: createbtnenabled =
isFirstnameValid &&
isLastnameValid &&
isEmailValidOrEmpty &&
isPhoneValidOrEmpty &&
((isAddress1Valid && iszipcodevalid && iscityvalid) ||
address_checked === false);
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
toast.loading($_("contact-is-being-added"));
let address = {};
if (address_checked === true) {
address = {
address1: address_input1_value,
address2: address_input2_value || "",
postalcode: address_zipcode_value,
city: address_city_value,
country: "DE",
};
}
let postdata = {
groups: selected_team,
firstname: firstname_input_value,
lastname: lastname_input_value,
address,
};
if (middlename_input_value) {
postdata.middlename = middlename_input_value;
}
if (phone_input_value) {
postdata.phone = phone_input_value;
}
if (email_input_value) {
postdata.email = email_input_value;
}
GroupContactService.groupContactControllerPost(postdata)
.then((result) => {
firstname_input_value = "";
lastname_input_value = "";
middlename_input_value = "";
email_input_value = "";
modal_open = false;
//
toast.dismiss();
toast.success($_("contact-added"));
current_contacts.push(result);
current_contacts = current_contacts;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw] relative z-10"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<div
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="size-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="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z"
/></svg
>
</div>
<div class="mt-3 sm:text-left max-h-[75vh] overflow-y-auto">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("create-a-new-contact")}
</h3>
<div class="mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-required-information-to-add-a-new-contact"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700"
>{$_("first-name")}</label
>
<input
use:focus
autocomplete="off"
placeholder={$_("first-name")}
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={firstname_input_value}
bind:this={firstname_input}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("first-name-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700"
>{$_("middle-name")}</label
>
<input
autocomplete="off"
placeholder={$_("middle-name")}
bind:value={middlename_input_value}
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label
for="lastname"
class="block text-sm font-medium text-gray-700"
>{$_("last-name")}</label
>
<input
autocomplete="off"
placeholder={$_("last-name")}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
bind:value={lastname_input_value}
bind:this={lastname_input}
type="text"
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("last-name-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="team"
class="block text-sm font-medium text-gray-700"
>{$_("teams")}</label
>
<select
name="team"
multiple
bind:value={selected_team}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
>
{#each teams as team}
<option value={team.id}>
{team.parentGroup.name}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value={org.id}>{org.name}</option>
{/each}
</select>
</div>
<div class="col-span-6">
<label
for="phone"
class="block text-sm font-medium text-gray-700"
>{$_("phone")}</label
>
<input
autocomplete="off"
placeholder={$_("phone")}
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
bind:value={phone_input_value}
bind:this={phone_input}
type="tel"
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{@html $_(
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number"
)}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="email"
class="block text-sm font-medium text-gray-700"
>{$_("e-mail-adress")}</label
>
<input
autocomplete="off"
placeholder={$_("e-mail-adress")}
class:border-red-500={!isEmailValidOrEmpty}
class:focus:border-red-500={!isEmailValidOrEmpty}
class:focus:ring-red-500={!isEmailValidOrEmpty}
bind:value={email_input_value}
bind:this={email_input}
type="email"
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isEmailValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-email-is-required")}
</span>
{/if}
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input
bind:checked={address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 size-4 text-indigo-600 border-gray-300 rounded"
/>
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-semibold text-gray-700"
>{$_("address")}</label
>
</div>
</div>
{#if address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700"
>{$_("address")}</label
>
<input
autocomplete="off"
placeholder={$_("address")}
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={address_input1_value}
bind:this={address_input1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("address-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700"
>{$_("apartment-suite-etc")}</label
>
<input
autocomplete="off"
placeholder={$_("apartment-suite-etc")}
bind:value={address_input2_value}
bind:this={address_input2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700"
>{$_("zip-postal-code")}</label
>
<input
autocomplete="off"
placeholder={$_("zip-postal-code")}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={address_zipcode_value}
bind:this={address_zipcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-zipcode-postal-code-is-required")}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700"
>{$_("city")}</label
>
<input
autocomplete="off"
placeholder={$_("city")}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={address_city_value}
bind:this={address_city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("valid-city-is-required")}
</span>
{/if}
</div>
{/if}
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="cancel_modal_button"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

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

View File

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

View File

@ -1,17 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import AddContactModal from "./AddContactModal.svelte";
import team_empty from "../teams/team_empty.svg";
let modal_open = false;
let current_contacts = [];
</script>
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={team_empty} alt="" />
<span class="font-bold">{$_("there-are-no-contacts-added-yet")}</span><br />
<span>{$_("add-your-first-contact")}</span>
</p>
</div>
<AddContactModal bind:modal_open bind:current_contacts />

View File

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

View File

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

View File

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

View File

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

View File

@ -1,380 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import {
DonationService,
DonorService,
RunnerService,
} from "@odit/lfk-client-js";
import Select from "svelte-select";
import { createEventDispatcher, onMount } from "svelte";
import toast from "svelte-french-toast";
export let modal_open;
const dispatch = createEventDispatcher();
const getDonorLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterDonors = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase());
$: donor = 0;
$: runner = 0;
$: donors = [];
$: runners = [];
$: type = "distance";
$: is_paid = false;
$: amount_input = 0;
$: processed_last_submit = true;
$: is_amount_valid = amount_input > 0;
$: createbtnenabled = is_amount_valid;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
let amount_cent = Math.floor(amount_input * 100);
processed_last_submit = false;
toast.loading($_("adding-donation"));
if (type === "fixed") {
let postdata = {
donor,
amount: amount_cent,
paidAmount: 0,
};
if (is_paid) {
postdata.paidAmount = amount_cent;
}
DonationService.donationControllerPostFixed(postdata)
.then((result) => {
donor = donors[0].id || 0;
runner = runners[0].id || 0;
amount_input = 0;
modal_open = false;
//
toast.dismiss();
toast.success($_("donation_added"));
dispatch("created", { donations: [result] });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
} else if (type === "anonymous") {
let postdata = {
amount: amount_cent,
};
DonationService.donationControllerPostAnonymous(postdata)
.then((result) => {
amount_input = 0;
modal_open = false;
//
toast.dismiss();
toast.success($_("donation_added"));
dispatch("created", { donations: [result] });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
} else if (type === "distance") {
let postdata = {
donor,
runner,
amountPerDistance: amount_cent,
};
DonationService.donationControllerPostDistance(postdata)
.then((result) => {
donor = donors[0].id || 0;
runner = runners[0].id || 0;
amount_input = 0;
modal_open = false;
//
toast.dismiss();
toast.success($_("donation_added"));
dispatch("created", { donations: [result] });
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
});
}
}
}
onMount(async () => {
donors = (await DonorService.donorControllerGetAll()).map((r) => {
return { label: getDonorLabel(r), value: r };
});
runners = (await RunnerService.runnerControllerGetAll()).map((r) => {
return { label: getDonorLabel(r), value: r };
});
});
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-neutral-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw] relative z-10"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class="">
<div
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="size-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</div>
<div class="mt-3">
<h3 class="text-xl leading-6 font-medium text-neutral-900">
{$_("add-donation")}
</h3>
<nav
class="relative z-0 flex border border-neutral-200 rounded-xl overflow-hidden mb-2"
>
<button
on:click={() => {
type = "distance";
}}
type="button"
id="bar-with-underline-item-1"
class:donation_active_tab={type === "distance"}
class:donation_inactive_tab={type !== "distance"}
aria-selected={type === "distance"}
role="tab"
>
{$_("spende_pro_km")}
</button>
<button
on:click={() => {
type = "fixed";
}}
type="button"
id="bar-with-underline-item-2"
class:donation_active_tab={type === "fixed"}
class:donation_inactive_tab={type !== "fixed"}
aria-selected={type === "fixed"}
role="tab"
>
{$_("festbetrag")}
</button>
<button
on:click={() => {
type = "anonymous";
}}
type="button"
id="bar-with-underline-item-3"
class:donation_active_tab={type === "anonymous"}
class:donation_inactive_tab={type !== "anonymous"}
aria-selected={type === "anonymous"}
role="tab"
>
{$_("anonyme_spende")}
</button>
</nav>
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
{#if type === "anonymous"}
<div class="col-span-6">
<label
for="donation_amount_eur"
class="block text-sm font-medium text-neutral-900"
>
{$_("donation-amount")}</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-neutral-300 border bg-neutral-50 text-neutral-800 p-2"
placeholder="2.00"
/>
<span
class="inline-flex items-center px-3 rounded-r-md border border-neutral-300 bg-neutral-50 text-neutral-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>
{:else}
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-neutral-900"
>{$_("donor")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={donors}
showChevron={true}
placeholder={$_("search-for-donor-name-or-id")}
noOptionsMessage={$_("no-donors-found")}
on:select={(selectedValue) =>
(donor = selectedValue.detail.value.id)}
on:clear={() => (donors = null)}
/>
</div>
{#if type === "distance"}
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-neutral-900"
>{$_("runner")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
on:select={(selectedValue) =>
(runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)}
/>
</div>
{/if}
<div class="col-span-6">
<label
for="donation_amount_eur"
class="block text-sm font-medium text-neutral-900"
>
{#if type === "fixed"}
{$_("donation-amount")}
{:else}{$_("amount-per-kilometer")}{/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-neutral-300 border bg-neutral-50 text-neutral-800 p-2"
placeholder="2.00"
/>
<span
class="inline-flex items-center px-3 rounded-r-md border border-neutral-300 bg-neutral-50 text-neutral-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}
{#if type === "fixed"}
<div class="flex">
<input
bind:checked={is_paid}
type="checkbox"
class="shrink-0 mt-0.5 border-neutral-200 rounded-sm text-blue-600 focus:ring-blue-500 checked:border-blue-500 disabled:opacity-50 disabled:pointer-events-none"
id="hs-default-checkbox"
/>
<label
for="hs-default-checkbox"
class="text-base text-neutral-900 ms-2 font-medium"
>{$_("already-paid")}</label
>
</div>
{/if}
</div>
</div>
</div>
</div>
<div
class="bg-neutral-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10"
>
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="w-full justify-center rounded-md border border-neutral-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-neutral-900 hover:bg-neutral-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 hidden lg:block"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

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

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