Compare commits

..

No commits in common. "1.8.0" and "1.7.0" have entirely different histories.
1.8.0 ... 1.7.0

77 changed files with 6518 additions and 7863 deletions

View File

@ -1,34 +0,0 @@
name: Build Latest and dev images
on:
push:
branches:
- dev
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@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:dev
${{ vars.REGISTRY }}/lfk/frontend:latest
platforms: linux/amd64,linux/arm64

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

26
.woodpecker/build.yml Normal file
View File

@ -0,0 +1,26 @@
steps:
- name: run full license export
image: registry.odit.services/hub/library/node:19.7.0-alpine3.16
commands:
- npm i -g pnpm@8
- pnpm i
- pnpm licenses:export
- name: build dev
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.odit.services/lfk/frontend
tags:
- dev
- latest
registry: registry.odit.services
platforms: linux/amd64,linux/arm64
cache_from: registry.odit.services/lfk/frontend:dev
username:
from_secret: odit-registry-builder-username
password:
from_secret: odit-registry-builder-password
when:
event:
- push
branch:
- dev

17
.woodpecker/release.yml Normal file
View File

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

View File

@ -2,37 +2,12 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.8.0](https://git.odit.services/lfk/frontend/compare/1.7.0...1.8.0)
- wip [`824ecfa`](https://git.odit.services/lfk/frontend/commit/824ecfab2e976cd7c6cd2851be8a9be5c6b686e1)
- wip [`0a6cf61`](https://git.odit.services/lfk/frontend/commit/0a6cf619b09be837d5503f4695250c7edaeeaff5)
- feat: improve fonts + button positions [`c37fb98`](https://git.odit.services/lfk/frontend/commit/c37fb98bed377744981e927ea8d22db9e20c55ca)
- wip [`1bc5314`](https://git.odit.services/lfk/frontend/commit/1bc53146b9f024f3cab613b227d29304d687c92b)
- wip [`e82350d`](https://git.odit.services/lfk/frontend/commit/e82350df4af082d2bbb322658c6c022d83b819ae)
- wip [`37cdbba`](https://git.odit.services/lfk/frontend/commit/37cdbba0a3563875e19bee560f2cd5c8fc2d7a6e)
- feat: improve input readability [`79e6a42`](https://git.odit.services/lfk/frontend/commit/79e6a4212d06029766d0a853686ed97879ebd349)
- wip [`5f5d827`](https://git.odit.services/lfk/frontend/commit/5f5d8277b98363ef15a92621fca0a209345aca95)
- chore(deps): bump [`bb2319a`](https://git.odit.services/lfk/frontend/commit/bb2319a78d253a2d6239a0d3daedc90fd29abdd0)
- feat: cleanup TeamDetail + OrgDetail [`f734d1e`](https://git.odit.services/lfk/frontend/commit/f734d1e3f643a500a6432a389c3103045cc51262)
- refactor(ci): Switch to actions for dev [`847fa28`](https://git.odit.services/lfk/frontend/commit/847fa288f1b5bbc422cc2944bbe66e80c5a00407)
- refactor(ci): Add Gitea workflow for building release images and remove Woodpecker configuration [`3ec18a6`](https://git.odit.services/lfk/frontend/commit/3ec18a696435ada26bf2de2220b190dc630a9759)
- feat: athiti font [`391186d`](https://git.odit.services/lfk/frontend/commit/391186d01f3b96638a3569dc2843bf181dc3f02c)
- fix(DonorDetail): donor deletion [`5147a20`](https://git.odit.services/lfk/frontend/commit/5147a20b3c4a46968482b1e3517047351c94f77e)
- feat(dashboard): full width for sidebar items [`975f145`](https://git.odit.services/lfk/frontend/commit/975f145444e5a478524ea2cbbfb9059b93617185)
- wip [`3d3ce29`](https://git.odit.services/lfk/frontend/commit/3d3ce2918bc20cf1080a2b5153ddd8aaf51374b4)
- feat(RunnerOrganizationService.runnerOrganizationControllerGetRunners): load all runners in org [`7c10d95`](https://git.odit.services/lfk/frontend/commit/7c10d95c1c68f4842fd323698e004a5ebf2c96cf)
- wip [`050a146`](https://git.odit.services/lfk/frontend/commit/050a146ae070d67d8308db4b9612fd6eacbb9923)
- fix(ci): Correct tag pattern syntax in release workflow [`e567bb3`](https://git.odit.services/lfk/frontend/commit/e567bb35c3b3f6eb73a2f0bc72f601e70f881ac8)
#### [1.7.0](https://git.odit.services/lfk/frontend/compare/1.6.0...1.7.0) #### [1.7.0](https://git.odit.services/lfk/frontend/compare/1.6.0...1.7.0)
> 17 December 2024
- refactor(pdfgeneration): Switch cards over to new service [`e230984`](https://git.odit.services/lfk/frontend/commit/e23098410c7d0b326cdbbb3a4b63fed10611e252) - refactor(pdfgeneration): Switch cards over to new service [`e230984`](https://git.odit.services/lfk/frontend/commit/e23098410c7d0b326cdbbb3a4b63fed10611e252)
- refactor(pdfgeneration): Switch to new document-server api [`878d971`](https://git.odit.services/lfk/frontend/commit/878d9714cbc0a60cfd96bd1faf8af6af46e6fb5e) - refactor(pdfgeneration): Switch to new document-server api [`878d971`](https://git.odit.services/lfk/frontend/commit/878d9714cbc0a60cfd96bd1faf8af6af46e6fb5e)
- refactor(pdfgeneration): Switched contract generation over to new document-server [`f99b7f4`](https://git.odit.services/lfk/frontend/commit/f99b7f4bb8f166bb966022ddd10689c082d248f0) - refactor(pdfgeneration): Switched contract generation over to new document-server [`f99b7f4`](https://git.odit.services/lfk/frontend/commit/f99b7f4bb8f166bb966022ddd10689c082d248f0)
- refactor(cards): Switched over to new document-server api [`65ce02e`](https://git.odit.services/lfk/frontend/commit/65ce02e777e6e9b3cfed248de680e5f292b3a639) - refactor(cards): Switched over to new document-server api [`65ce02e`](https://git.odit.services/lfk/frontend/commit/65ce02e777e6e9b3cfed248de680e5f292b3a639)
- 🚀RELEASE v1.7.0 [`ae056cd`](https://git.odit.services/lfk/frontend/commit/ae056cd88cb27f003845fa4534553cde841c7f99)
- fix(pdfgeneration): Added parent_group [`7f989b2`](https://git.odit.services/lfk/frontend/commit/7f989b206b16e2687d01a38da8e3ea9be0a52ba5) - fix(pdfgeneration): Added parent_group [`7f989b2`](https://git.odit.services/lfk/frontend/commit/7f989b206b16e2687d01a38da8e3ea9be0a52ba5)
#### [1.6.0](https://git.odit.services/lfk/frontend/compare/1.5.3...1.6.0) #### [1.6.0](https://git.odit.services/lfk/frontend/compare/1.5.3...1.6.0)

View File

@ -1,4 +1,4 @@
FROM registry.odit.services/hub/library/node:23.10.0-alpine3.21 AS build FROM registry.odit.services/hub/library/node:23.2.0-alpine3.20 AS build
ARG NPM_REGISTRY_URL=https://registry.npmjs.org ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app WORKDIR /app

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "1.8.0", "version": "1.7.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",
@ -15,10 +15,10 @@
"@odit/license-exporter": "0.2.0", "@odit/license-exporter": "0.2.0",
"@sveltejs/vite-plugin-svelte": "2.1.1", "@sveltejs/vite-plugin-svelte": "2.1.1",
"auto-changelog": "2.5.0", "auto-changelog": "2.5.0",
"autoprefixer": "10.4.21", "autoprefixer": "10.4.20",
"postcss": "8.5.3", "postcss": "8.4.49",
"prettier": "3.5.3", "prettier": "3.3.3",
"prettier-plugin-svelte": "3.3.3", "prettier-plugin-svelte": "3.2.8",
"release-it": "17.10.0", "release-it": "17.10.0",
"svelte-select": "3.17.0", "svelte-select": "3.17.0",
"tailwindcss": "3.4.15", "tailwindcss": "3.4.15",
@ -42,7 +42,6 @@
} }
}, },
"dependencies": { "dependencies": {
"@fontsource/athiti": "^5.2.5",
"@odit/lfk-client-js": "1.1.3", "@odit/lfk-client-js": "1.1.3",
"@paralleldrive/cuid2": "2.2.2", "@paralleldrive/cuid2": "2.2.2",
"@tanstack/svelte-table": "8.9.1", "@tanstack/svelte-table": "8.9.1",
@ -55,7 +54,7 @@
"svelte-french-toast": "1.2.0", "svelte-french-toast": "1.2.0",
"svelte-i18n": "3.6.0", "svelte-i18n": "3.6.0",
"tinro": "0.6.12", "tinro": "0.6.12",
"validator": "13.15.0", "validator": "13.12.0",
"xlsx": "0.18.5" "xlsx": "0.18.5"
}, },
"volta": { "volta": {

3510
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because one or more lines are too long

View File

@ -163,7 +163,7 @@
type="number" type="number"
step="1" step="1"
name="amount" 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" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="400" placeholder="400"
/> />
<span <span

View File

@ -148,7 +148,7 @@
>{$_("runner")}</label >{$_("runner")}</label
> >
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)} filterRunners(label, filterText, option)}
items={runners} items={runners}

View File

@ -139,7 +139,7 @@
>{$_("runner")}</label >{$_("runner")}</label
> >
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)} filterRunners(label, filterText, option)}
items={runners} items={runners}

View File

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

View File

@ -11,7 +11,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("cards")} {$_("cards")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:CREATE")}

View File

@ -208,7 +208,7 @@
bind:this={firstname_input} bind:this={firstname_input}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isFirstnameValid} {#if !isFirstnameValid}
<span <span
@ -231,7 +231,7 @@
bind:this={middlename_input} bind:this={middlename_input}
type="text" type="text"
name="trackname" name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
@ -250,7 +250,7 @@
bind:this={lastname_input} bind:this={lastname_input}
type="text" type="text"
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isLastnameValid} {#if !isLastnameValid}
<span <span
@ -270,7 +270,7 @@
name="team" name="team"
multiple multiple
bind:value={selected_team} bind:value={selected_team}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
> >
{#each teams as team} {#each teams as team}
<option value={team.id}> <option value={team.id}>
@ -300,7 +300,7 @@
bind:this={phone_input} bind:this={phone_input}
type="tel" type="tel"
name="phone" name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isPhoneValidOrEmpty} {#if !isPhoneValidOrEmpty}
<span <span
@ -328,7 +328,7 @@
bind:this={email_input} bind:this={email_input}
type="email" type="email"
name="email" name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isEmailValidOrEmpty} {#if !isEmailValidOrEmpty}
<span <span
@ -349,7 +349,7 @@
/> />
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label for="comments" class="font-semibold text-gray-700" <label for="comments" class="font-medium text-gray-700"
>{$_("address")}</label >{$_("address")}</label
> >
</div> </div>
@ -371,7 +371,7 @@
bind:this={address_input1} bind:this={address_input1}
type="text" type="text"
name="address1" name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isAddress1Valid} {#if !isAddress1Valid}
<span <span
@ -394,7 +394,7 @@
bind:this={address_input2} bind:this={address_input2}
type="text" type="text"
name="address2" name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
@ -413,7 +413,7 @@
bind:this={address_zipcode} bind:this={address_zipcode}
type="text" type="text"
name="zipcode" name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !iszipcodevalid} {#if !iszipcodevalid}
<span <span
@ -439,7 +439,7 @@
bind:this={address_city} bind:this={address_city}
type="text" type="text"
name="city" name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !iscityvalid} {#if !iscityvalid}
<span <span

View File

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

View File

@ -8,7 +8,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("contacts")} {$_("contacts")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:CREATE")}

View File

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

View File

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

View File

@ -7,7 +7,7 @@
</script> </script>
<div class="p-2 md:p-5 overflow-x-hidden"> <div class="p-2 md:p-5 overflow-x-hidden">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("dashboard-greeting")} <span class="text-blue-500" {$_("dashboard-greeting")} <span class="text-blue-500"
>{store.state.jwtinfo.userdetails.firstname} >{store.state.jwtinfo.userdetails.firstname}
{store.state.jwtinfo.userdetails.lastname}</span {store.state.jwtinfo.userdetails.lastname}</span

View File

@ -192,7 +192,7 @@
>{$_("donor")}</label >{$_("donor")}</label
> >
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)} filterDonors(label, filterText, option)}
items={donors} items={donors}
@ -212,7 +212,7 @@
>{$_("runner")}</label >{$_("runner")}</label
> >
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)} filterDonors(label, filterText, option)}
items={runners} items={runners}
@ -244,7 +244,7 @@
type="number" type="number"
step="0.01" step="0.01"
name="donation_amount_eur" name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 p-2" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="2.00" placeholder="2.00"
/> />
<span <span

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("donations")} {$_("donations")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")}

View File

@ -196,7 +196,7 @@
bind:this={firstname_input} bind:this={firstname_input}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isFirstnameValid} {#if !isFirstnameValid}
<span <span
@ -219,7 +219,7 @@
bind:this={middlename_input} bind:this={middlename_input}
type="text" type="text"
name="trackname" name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
@ -238,7 +238,7 @@
bind:this={lastname_input} bind:this={lastname_input}
type="text" type="text"
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isLastnameValid} {#if !isLastnameValid}
<span <span
@ -264,7 +264,7 @@
bind:this={phone_input} bind:this={phone_input}
type="tel" type="tel"
name="phone" name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isPhoneValidOrEmpty} {#if !isPhoneValidOrEmpty}
<span <span
@ -292,7 +292,7 @@
bind:this={email_input} bind:this={email_input}
type="email" type="email"
name="email" name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isEmailValidOrEmpty} {#if !isEmailValidOrEmpty}
<span <span
@ -313,7 +313,7 @@
/> />
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label for="comments" class="font-semibold text-gray-700" <label for="comments" class="font-medium text-gray-700"
>{$_("receipt-needed")}</label >{$_("receipt-needed")}</label
> >
</div> </div>
@ -335,7 +335,7 @@
bind:this={address_input1} bind:this={address_input1}
type="text" type="text"
name="address1" name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isAddress1Valid} {#if !isAddress1Valid}
<span <span
@ -358,7 +358,7 @@
bind:this={address_input2} bind:this={address_input2}
type="text" type="text"
name="address2" name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
@ -377,7 +377,7 @@
bind:this={address_zipcode} bind:this={address_zipcode}
type="text" type="text"
name="zipcode" name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !iszipcodevalid} {#if !iszipcodevalid}
<span <span
@ -403,7 +403,7 @@
bind:this={address_city} bind:this={address_city}
type="text" type="text"
name="city" name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !iscityvalid} {#if !iscityvalid}
<span <span

View File

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

View File

@ -18,7 +18,7 @@
{:else} {:else}
<a <a
href="../donations/{donation.id}" href="../donations/{donation.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-green-700 text-white mr-1" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1"
>{$_("fixed-donation")}: >{$_("fixed-donation")}:
{(donation.amount / 100) {(donation.amount / 100)
.toFixed(2) .toFixed(2)

View File

@ -9,7 +9,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("donors")} {$_("donors")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:CREATE")}

View File

@ -97,7 +97,7 @@
{/if} {/if}
<!-- /// --> <!-- /// -->
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("about")} {$_("about")}
</h4> </h4>
<p class="mt-2 mb-2"> <p class="mt-2 mb-2">
@ -109,7 +109,7 @@
<br /> <br />
<span>{$_("lfk-is-os")}</span> <span>{$_("lfk-is-os")}</span>
</p> </p>
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("credits")} {$_("credits")}
</h4> </h4>
<p class="text-left">{$_("oss_credit_description")}</p> <p class="text-left">{$_("oss_credit_description")}</p>

View File

@ -130,7 +130,7 @@
bind:value={name_input_value} bind:value={name_input_value}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isNameValid} {#if !isNameValid}
<span <span
@ -152,7 +152,7 @@
bind:value={description_input_value} bind:value={description_input_value}
type="text" type="text"
name="trackname" name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
</div> </div>

View File

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

View File

@ -133,8 +133,10 @@
</nav> </nav>
</div> </div>
</div> </div>
<div class="mb-4 text-3xl font-extrabold"> <div class="mb-8 text-3xl font-extrabold">
<div> {$_("permissions")}:
{original_data.name}
<span>
{#if promises.length === 0} {#if promises.length === 0}
<button <button
disabled={save_enabled} disabled={save_enabled}
@ -151,7 +153,7 @@
>{$_("applying-changes")}</button >{$_("applying-changes")}</button
> >
{/if} {/if}
</div> </span>
</div> </div>
<!-- --> <!-- -->
<div class="flex flex-wrap -mx-1 overflow-hidden"> <div class="flex flex-wrap -mx-1 overflow-hidden">

View File

@ -8,7 +8,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("user-groups")} {$_("user-groups")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:CREATE")}

View File

@ -153,7 +153,7 @@
bind:this={name_input_dom} bind:this={name_input_dom}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isOrgnameValid} {#if !isOrgnameValid}
<span <span
@ -174,7 +174,7 @@
/> />
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label for="comments" class="font-semibold text-gray-700" <label for="comments" class="font-medium text-gray-700"
>{$_("address")}</label >{$_("address")}</label
> >
</div> </div>
@ -196,7 +196,7 @@
bind:this={address_input1} bind:this={address_input1}
type="text" type="text"
name="address1" name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isAddress1Valid} {#if !isAddress1Valid}
<span <span
@ -219,7 +219,7 @@
bind:this={address_input2} bind:this={address_input2}
type="text" type="text"
name="address2" name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
@ -238,7 +238,7 @@
bind:this={address_zipcode} bind:this={address_zipcode}
type="text" type="text"
name="zipcode" name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !iszipcodevalid} {#if !iszipcodevalid}
<span <span
@ -264,7 +264,7 @@
bind:this={address_city} bind:this={address_city}
type="text" type="text"
name="city" name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !iscityvalid} {#if !iscityvalid}
<span <span

View File

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

View File

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

View File

@ -10,7 +10,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("organizations")} {$_("organizations")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:CREATE")}

View File

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

View File

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

View File

@ -1,180 +1,224 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { import {
DonationService, DonationService,
RunnerTeamService, RunnerTeamService,
RunnerOrganizationService, RunnerOrganizationService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import { init } from "@paralleldrive/cuid2"; import { init } from "@paralleldrive/cuid2";
import toast from "svelte-french-toast"; import toast from "svelte-french-toast";
import DocumentServer from "./DocumentServer"; import DocumentServer from "./DocumentServer";
const createId = init({ length: 10, fingerprint: "lfk-frontend" }); const createId = init({ length: 10, fingerprint: "lfk-frontend" });
const documentServer = new DocumentServer( const documentServer = new DocumentServer(config.baseurl_documentserver,config.documentserver_key);
config.baseurl_documentserver,
config.documentserver_key
);
export let certificates_show = false;
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
function generateCertificates(locale) { export let certificates_show = false;
if (generate_orgs.length > 0) { export let generate_runners = [];
generateOrgCertificates(locale); export let generate_orgs = [];
} else if (generate_teams.length > 0) { export let generate_teams = [];
generateTeamCertificates(locale); $: certificates_dropdown_open = false;
} else { document.addEventListener("click", function (e) {
generateRunnerCertificates(locale); if (
} e.target.parentNode?.parentNode?.id != "certificates:dropdown" &&
} e.target.parentNode?.parentNode?.id != "certificates:dropdown:menu"
function download(blob, fileName) { ) {
const url = window.URL.createObjectURL(blob); certificates_dropdown_open = false;
let a = document.createElement("a"); }
a.href = url; });
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
toast.dismiss();
toast($_("pdf-successfully-generated"));
}
async function generateRunnerCertificates(locale) { function generateCertificates(locale) {
toast.loading($_("generating-pdf")); certificates_dropdown_open = false;
const current_donations =
(await DonationService.donationControllerGetAll()) || [];
let certificateRunners = [];
for (let runner of generate_runners) {
runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
documentServer
.generateCertificates(certificateRunners, locale)
.then((blob) => {
let fileName = `${$_("certificates")}-${locale}.pdf`;
if (generate_runners.length == 1) {
fileName = `${$_("certificates")}_${
generate_runners[0].firstname
}_${generate_runners[0].lastname}-${locale}-${createId()}.pdf`;
}
download(blob, fileName);
})
.catch((err) => {});
}
async function generateTeamCertificates(locale) { if (generate_orgs.length > 0) {
toast.loading($_("generating-pdfs")); generateOrgCertificates(locale);
let count = 0; } else if (generate_teams.length > 0) {
const current_donations = generateTeamCertificates(locale);
(await DonationService.donationControllerGetAll()) || []; } else {
for (const t of generate_teams) { generateRunnerCertificates(locale);
const runners = await RunnerTeamService.runnerTeamControllerGetRunners( }
t.id }
); function download (blob, fileName){
let certificateRunners = []; const url = window.URL.createObjectURL(blob);
for (let runner of runners) { let a = document.createElement("a");
runner.distanceDonations = a.href = url;
current_donations.filter((d) => d.runner?.id == runner.id) || []; a.download = fileName;
certificateRunners.push(runner); document.body.appendChild(a);
} a.click();
documentServer a.remove();
.generateCertificates(certificateRunners, locale) toast.dismiss();
.then((blob) => { toast($_("pdf-successfully-generated"));
count++; }
download(
blob,
`${$_("certificates")}_${t.name}-${locale}-${createId()}.pdf`
);
})
.catch((err) => {});
}
}
async function generateOrgCertificates(locale) { async function generateRunnerCertificates(locale) {
toast.loading($_("generating-pdfs")); toast.loading($_("generating-pdf"));
const current_donations = const current_donations =
(await DonationService.donationControllerGetAll()) || []; (await DonationService.donationControllerGetAll()) || [];
let count = 0; let certificateRunners = [];
let count_orgs = 0; for (let runner of generate_runners) {
for (const o of generate_orgs) { runner.distanceDonations =
count_orgs++; current_donations.filter((d) => d.runner?.id == runner.id) || [];
let count = 0; certificateRunners.push(runner);
let runners = }
await RunnerOrganizationService.runnerOrganizationControllerGetRunners( documentServer.generateCertificates(certificateRunners, locale)
o.id, .then((blob) => {
false let fileName = `${$_("certificates")}-${locale}.pdf`
); if (generate_runners.length == 1) {
let certificateRunners = []; fileName = `${$_("certificates")}_${
for (let runner of runners) { generate_runners[0].firstname
runner.distanceDonations = }_${generate_runners[0].lastname}-${locale}-${createId()}.pdf`;
current_donations.filter((d) => d.runner?.id == runner.id) || []; }
certificateRunners.push(runner); download(blob, fileName);
} })
await documentServer .catch((err) => {});
.generateCertificates(certificateRunners, locale) }
.then((blob) => {
download( async function generateTeamCertificates(locale) {
blob, toast.loading($_("generating-pdfs"));
`${$_("certificates")}_${o.name}-${locale}-${createId()}.pdf` let count = 0;
); const current_donations =
}) (await DonationService.donationControllerGetAll()) || [];
.catch((err) => {}); for (const t of generate_teams) {
for (const t of o.teams) { const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
count++; t.id
let runners = await RunnerTeamService.runnerTeamControllerGetRunners( );
t.id let certificateRunners = [];
); for (let runner of runners) {
let certificateRunners = []; runner.distanceDonations =
for (let runner of runners) { current_donations.filter((d) => d.runner?.id == runner.id) || [];
runner.distanceDonations = certificateRunners.push(runner);
current_donations.filter((d) => d.runner?.id == runner.id) || []; }
certificateRunners.push(runner); documentServer.generateCertificates(certificateRunners, locale)
} .then((blob) => {
await documentServer count++;
.generateCertificates(certificateRunners, locale) download(blob, `${$_("certificates")}_${
.then((blob) => { t.name
download( }-${locale}-${createId()}.pdf`)
blob, })
`${$_("certificates")}_${o.name}_${ .catch((err) => {});
t.name }
}-${locale}-${createId()}.pdf` }
);
if ( async function generateOrgCertificates(locale) {
count === o.teams.length && toast.loading($_("generating-pdfs"));
count_orgs === generate_orgs.length const current_donations =
) { (await DonationService.donationControllerGetAll()) || [];
toast.dismiss(); let count = 0;
toast($_("pdfs-successfully-generated")); let count_orgs = 0;
} for (const o of generate_orgs) {
}) count_orgs++;
.catch((err) => {}); let count = 0;
} let runners =
} await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
} o.id,
true
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await documentServer.generateCertificates(certificateRunners, locale)
.then((blob) => {
download(blob, `${$_("certificates")}_${
o.name
}-${locale}-${createId()}.pdf`)
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await documentServer.generateCertificates(certificateRunners, locale)
.then((blob) => {
download(blob, `${$_("certificates")}_${o.name}_${
t.name
}-${locale}-${createId()}.pdf`)
if (
count === o.teams.length &&
count_orgs === generate_orgs.length
) {
toast.dismiss();
toast($_("pdfs-successfully-generated"));
}
})
.catch((err) => {});
}
}
}
</script> </script>
{#if certificates_show} {#if certificates_show}
<div> <div id="certificates:dropdown" class="relative inline-block">
<p class="text-base">{$_("generate-runner-certificates")}</p> <div>
<div class="inline-flex rounded-lg shadow-2xs"> <button
<button on:click={() => {
on:click={() => { certificates_dropdown_open = !certificates_dropdown_open;
generateCertificates("de"); }}
}} type="button"
class="py-3 px-4 inline-flex items-center gap-x-2 -ms-px first:rounded-s-lg first:ms-0 last:rounded-e-lg text-sm font-medium focus:z-10 border border-gray-200 bg-blue-600 text-white shadow-2xs hover:bg-blue-800 focus:outline-hidden focus:bg-blue-800 disabled:opacity-50 disabled:pointer-events-none" class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
> id="options-menu"
DE aria-haspopup="true"
</button> aria-expanded="true"
<button >
on:click={() => { {$_("generate-runner-certificates")}
generateCertificates("en"); <svg
}} xmlns="http://www.w3.org/2000/svg"
class="py-3 px-4 inline-flex items-center gap-x-2 -ms-px first:rounded-s-lg first:ms-0 last:rounded-e-lg text-sm font-medium focus:z-10 border border-gray-200 bg-blue-600 text-white shadow-2xs hover:bg-blue-800 focus:outline-hidden focus:bg-blue-800 disabled:opacity-50 disabled:pointer-events-none" width="24"
> height="24"
EN viewBox="0 0 24 24"
</button> class="-mr-1 ml-2 h-5 w-5"
</div> ><path fill="none" d="M0 0h24v24H0z" />
</div> <path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"
/></svg
>
</button>
</div>
{#if certificates_dropdown_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10"
id="certificates:dropdown:menu"
>
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu"
>
<span class="block w-full text-left px-4 py-2 text-sm text-gray-700"
>{$_("select-language")}</span
>
<button
on:click={() => {
generateCertificates("de");
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem"
>
{$_("german")}
</button>
<button
on:click={() => {
generateCertificates("en");
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem"
>
{$_("english")}
</button>
</div>
</div>
{/if}
</div>
{/if} {/if}

View File

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

View File

@ -66,7 +66,7 @@
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<span <span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-green-100 text-green-800" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
> >
Active Active
</span> </span>

View File

@ -189,7 +189,7 @@
bind:this={firstname_input} bind:this={firstname_input}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isFirstnameValid} {#if !isFirstnameValid}
<span <span
@ -212,7 +212,7 @@
bind:this={middlename_input} bind:this={middlename_input}
type="text" type="text"
name="trackname" name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
@ -231,7 +231,7 @@
bind:this={lastname_input} bind:this={lastname_input}
type="text" type="text"
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isLastnameValid} {#if !isLastnameValid}
<span <span
@ -248,7 +248,7 @@
>{$_("team")}</label >{$_("team")}</label
> >
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) || label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id option.value.id
@ -281,7 +281,7 @@
bind:this={phone_input} bind:this={phone_input}
type="tel" type="tel"
name="phone" name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isPhoneValidOrEmpty} {#if !isPhoneValidOrEmpty}
<span <span
@ -309,7 +309,7 @@
bind:this={email_input} bind:this={email_input}
type="email" type="email"
name="email" name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isEmailValidOrEmpty} {#if !isEmailValidOrEmpty}
<span <span

View File

@ -268,7 +268,7 @@
<select <select
name="team" name="team"
bind:value={selected_org} bind:value={selected_org}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
> >
{#each passed_orgs as o} {#each passed_orgs as o}
<option value={o.id}>{o.name}</option> <option value={o.id}>{o.name}</option>
@ -279,7 +279,7 @@
{#if opened_from === "RunnerOverview"} {#if opened_from === "RunnerOverview"}
<p>{$_("group")}</p> <p>{$_("group")}</p>
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) || label.toLowerCase().includes(filterText.toLowerCase()) ||
option.id.value option.id.value

View File

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

View File

@ -11,7 +11,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("runners")} {$_("runners")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}

View File

@ -131,7 +131,7 @@
>{$_("runner")}</label >{$_("runner")}</label
> >
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)} filterRunners(label, filterText, option)}
items={runners} items={runners}
@ -160,7 +160,7 @@
type="number" type="number"
step="1" step="1"
name="donation_amount_eur" name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 p-2" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="400" placeholder="400"
/> />
<span <span

View File

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

View File

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

View File

@ -9,7 +9,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("scans")} {$_("scans")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:CREATE")}

View File

@ -133,7 +133,7 @@
class="block text-sm font-medium text-gray-700">Track</label class="block text-sm font-medium text-gray-700">Track</label
> >
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) || label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id option.value.id
@ -161,11 +161,11 @@
bind:value={description} bind:value={description}
type="text" type="text"
name="description" name="description"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label for="enabled" class="font-semibold text-gray-700" <label for="enabled" class="font-medium text-gray-700"
>{$_("enabled_large")}</label >{$_("enabled_large")}</label
> >
<br /> <br />

View File

@ -119,7 +119,7 @@
<p <p
name="token" name="token"
class:bg-green-200={copied} class:bg-green-200={copied}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
> >
{new_station.key} {new_station.key}
</p> </p>

View File

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

View File

@ -24,7 +24,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("scanstations")} {$_("scanstations")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("STATION:CREATE")}
@ -111,7 +111,7 @@
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
<a <a
href="../tracks" href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 border border-current" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
> >
{s.track.name || s.track.distance + "m"}</a {s.track.name || s.track.distance + "m"}</a
> >
@ -132,12 +132,12 @@
<div class="flex items-center"> <div class="flex items-center">
{#if s.enabled} {#if s.enabled}
<span <span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-green-100 text-green-800" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("active")}</span >{$_("active")}</span
> >
{:else} {:else}
<span <span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border border-current bg-red-100 text-red-800" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("inactive")}</span >{$_("inactive")}</span
> >
{/if} {/if}

View File

@ -62,13 +62,21 @@
</script> </script>
<ConfirmProfileDeletion bind:modal_open bind:delete_triggered /> <ConfirmProfileDeletion bind:modal_open bind:delete_triggered />
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8">
<h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl"
>
🔨<br />{$_("settings")}
</h1>
</div>
</div>
<div class="pt-0 pb-16 bg-gray-50 overflow-hidden lg:pt-12 lg:py-24"> <div class="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"> <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<span class="text-3xl font-bold">{$_("settings")}</span>
<div> <div>
<div class="md:grid md:grid-cols-3 md:gap-6"> <div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1"> <div class="md:col-span-1">
<div class="sm:px-0"> <div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900"> <h3 class="text-lg font-medium leading-6 text-gray-900">
{$_("profile")} {$_("profile")}
</h3> </h3>
@ -83,8 +91,8 @@
<div class="mt-5 md:mt-0 md:col-span-2"> <div class="mt-5 md:mt-0 md:col-span-2">
<div class="shadow sm:rounded-md sm:overflow-hidden"> <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="px-4 py-5 bg-white space-y-6 sm:p-6">
<div class="text-sm w-full mt-2"> <div class="text-sm w-full">
<label for="username" class="font-semibold text-gray-700" <label for="username" class="font-medium text-gray-700"
>{$_("username")}</label >{$_("username")}</label
> >
<input <input
@ -93,11 +101,11 @@
type="text" type="text"
bind:value={editable.username} bind:value={editable.username}
name="username" name="username"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="text-sm w-full mt-2"> <div class="text-sm w-full">
<label for="email" class="font-semibold text-gray-700" <label for="email" class="font-medium text-gray-700"
>{$_("e-mail-adress")}</label >{$_("e-mail-adress")}</label
> >
<input <input
@ -106,7 +114,7 @@
type="email" type="email"
bind:value={editable.email} bind:value={editable.email}
name="email" name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
{#if !isEmail(editable.email)} {#if !isEmail(editable.email)}
@ -115,8 +123,8 @@
>{$_("valid-email-is-required")}</span >{$_("valid-email-is-required")}</span
> >
{/if} {/if}
<div class="text-sm w-full mt-2"> <div class="text-sm w-full">
<label for="firstname" class="font-semibold text-gray-700" <label for="firstname" class="font-medium text-gray-700"
>{$_("first-name")}</label >{$_("first-name")}</label
> >
<input <input
@ -125,11 +133,11 @@
type="text" type="text"
bind:value={editable.firstname} bind:value={editable.firstname}
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<!-- <div class="text-sm w-full mt-2"> <!-- <div class="text-sm w-full">
<label for="middlename" class="font-semibold text-gray-700" <label for="middlename" class="font-medium text-gray-700"
>{$_("middle-name")}</label >{$_("middle-name")}</label
> >
<input <input
@ -138,11 +146,11 @@
type="text" type="text"
bind:value={editable.middlename} bind:value={editable.middlename}
name="middlename" name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> --> </div> -->
<div class="text-sm w-full mt-2"> <div class="text-sm w-full">
<label for="lastname" class="font-semibold text-gray-700" <label for="lastname" class="font-medium text-gray-700"
>{$_("last-name")}</label >{$_("last-name")}</label
> >
<input <input
@ -151,7 +159,7 @@
type="text" type="text"
bind:value={editable.lastname} bind:value={editable.lastname}
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
</div> </div>
@ -176,7 +184,7 @@
<div> <div>
<div class="md:grid md:grid-cols-3 md:gap-6"> <div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1"> <div class="md:col-span-1">
<div class="sm:px-0"> <div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900"> <h3 class="text-lg font-medium leading-6 text-gray-900">
{$_("password")} {$_("password")}
</h3> </h3>
@ -191,7 +199,7 @@
<div class="mt-5 md:mt-0 md:col-span-2"> <div class="mt-5 md:mt-0 md:col-span-2">
<div class="shadow sm:rounded-md sm:overflow-hidden"> <div class="shadow sm:rounded-md sm:overflow-hidden">
<div class="px-4 py-3 bg-gray-50 text-left sm:px-6"> <div class="px-4 py-3 bg-gray-50 text-left sm:px-6">
<label for="new_password" class="font-semibold text-gray-700" <label for="new_password" class="font-medium text-gray-700"
>{$_("new-password")}</label >{$_("new-password")}</label
> >
<div class="-mt-px relative"> <div class="-mt-px relative">
@ -204,7 +212,7 @@
placeholder={$_("password")} placeholder={$_("password")}
/> />
</div> </div>
<label for="new_password" class="font-semibold text-gray-700" <label for="new_password" class="font-medium text-gray-700"
>{$_("confirm-the-new-password")}</label >{$_("confirm-the-new-password")}</label
> >
<div class="-mt-px relative"> <div class="-mt-px relative">
@ -247,7 +255,7 @@
<div> <div>
<div class="md:grid md:grid-cols-3 md:gap-6"> <div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1"> <div class="md:col-span-1">
<div class="sm:px-0"> <div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900"> <h3 class="text-lg font-medium leading-6 text-gray-900">
{$_("danger-zone")} {$_("danger-zone")}
</h3> </h3>

View File

@ -126,7 +126,7 @@
bind:value={description} bind:value={description}
type="text" type="text"
name="description" name="description"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
</div> </div>

View File

@ -96,7 +96,7 @@
<p <p
name="token" name="token"
class:bg-green-200={copied} class:bg-green-200={copied}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
> >
{new_client.key} {new_client.key}
</p> </p>

View File

@ -1,110 +1,124 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import store from "../../store"; import store from "../../store";
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import ConfirmStatsClientDeletion from "./ConfirmStatsClientDeletion.svelte"; import ConfirmStatsClientDeletion from "./ConfirmStatsClientDeletion.svelte";
import { StatsClientService } from "@odit/lfk-client-js"; import { StatsClientService } from "@odit/lfk-client-js";
import toast from "svelte-french-toast"; import toast from "svelte-french-toast";
let data_loaded = false; let data_loaded = false;
let modal_open; let modal_open;
let delete_client; let delete_client;
export let params; export let params;
$: delete_triggered = false; $: delete_triggered = false;
$: original_data = {}; $: original_data = {};
const promise = StatsClientService.statsClientControllerGetOne( const promise = StatsClientService.statsClientControllerGetOne(
params.clientid params.clientid
).then((data) => { ).then((data) => {
data_loaded = true; data_loaded = true;
original_data = Object.assign(original_data, data); original_data = Object.assign(original_data, data);
}); });
function deleteClient() { function deleteClient() {
StatsClientService.statsClientControllerRemove(original_data.id, false) StatsClientService.statsClientControllerRemove(original_data.id, false)
.then((resp) => { .then((resp) => {
toast($_("statsclient-deleted")); toast($_("statsclient-deleted"));
location.replace("./"); location.replace("./");
}) })
.catch((err) => { .catch((err) => {
modal_open = true; modal_open = true;
delete_client = original_data; delete_client = original_data;
}); });
} }
</script> </script>
<ConfirmStatsClientDeletion bind:modal_open bind:delete_client /> <ConfirmStatsClientDeletion bind:modal_open bind:delete_client />
{#await promise} {#await promise}
{$_("loading-statsclient-details")} {$_("loading-statsclient-details")}
{:then} {:then}
<section class="container p-5 select-none"> <section class="container p-5 select-none">
<div class="flex flex-row mb-4"> <div class="flex flex-row mb-4">
<div class="w-full"> <div class="w-full">
<nav class="w-full flex"> <nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start"> <ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="./" <svg
><svg fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" viewBox="0 0 24 24"
height="24" width="24"
viewBox="0 0 24 24" height="24"
fill="none" ><path fill="none" d="M0 0h24v24H0z" />
stroke="currentColor" <path
stroke-width="2" d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
stroke-linecap="round" /></svg
stroke-linejoin="round" >
class="inline-block" </li>
><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg <li class="flex items-center ml-2">
> <a class="mr-2" href="./">{$_("statsclient")}</a><svg
{$_("statsclients")}</a stroke="currentColor"
> fill="none"
</li> stroke-width="2"
</ol> viewBox="0 0 24 24"
</nav> stroke-linecap="round"
</div> stroke-linejoin="round"
</div> class="h-3 w-3 mr-2 stroke-current"
<div class="mb-4 text-3xl font-extrabold leading-tight"> height="1em"
{$_("statsclient")} #{original_data.id} width="1em"
<div data-id="stations_actions_${original_data.id}"> xmlns="http://www.w3.org/2000/svg"
{#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:DELETE")} ><line x1="5" y1="12" x2="19" y2="12" />
{#if delete_triggered} <polyline points="12 5 19 12 12 19" /></svg
<button >
on:click={deleteClient} </li>
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" <li class="flex items-center">
>{$_("confirm-deletion")}</button <span class="mr-2">#{original_data.id}</span>
> </li>
<button </ol>
on:click={() => { </nav>
delete_triggered = !delete_triggered; </div>
}} </div>
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" <div class="mb-8 text-3xl font-extrabold leading-tight">
>{$_("cancel")}</button #{original_data.id}
> <span data-id="stations_actions_${original_data.id}">
{/if} {#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:DELETE")}
{#if !delete_triggered} {#if delete_triggered}
<button <button
on:click={() => { on:click={deleteClient}
delete_triggered = true; class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
}} >{$_("confirm-deletion")}</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" <button
>{$_("delete-statsclient")}</button on:click={() => {
> delete_triggered = !delete_triggered;
{/if} }}
{/if} 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"
</div> >{$_("cancel")}</button
</div> >
<!-- --> {/if}
<div class="text-sm w-full mt-2"> {#if !delete_triggered}
<label for="description" class="font-semibold text-gray-700" <button
>{$_("description")}</label on:click={() => {
> delete_triggered = true;
<p }}
name="description" type="button"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" 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-statsclient")}</button
{original_data.description} >
</p> {/if}
</div> {/if}
</section> </span>
</div>
<!-- -->
<div class="text-sm w-full">
<label for="description" class="font-medium text-gray-700"
>{$_("description")}</label
>
<p
name="description"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
>
{original_data.description}
</p>
</div>
</section>
{:catch error} {:catch error}
<PromiseError {error} /> <PromiseError {error} />
{/await} {/await}

View File

@ -11,7 +11,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("statsclients")} {$_("statsclients")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:CREATE")}

View File

@ -141,7 +141,7 @@
bind:this={teamname_input_dom} bind:this={teamname_input_dom}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-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 !isTeamNameValid} {#if !isTeamNameValid}
<span <span
@ -158,7 +158,7 @@
>{$_("organization")}</label >{$_("organization")}</label
> >
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) || label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id option.value.id

View File

@ -1,264 +1,308 @@
<script> <script>
import { import {
GroupContactService, GroupContactService,
RunnerOrganizationService, RunnerOrganizationService,
RunnerTeamService, RunnerTeamService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import toast from "svelte-french-toast"; import toast from 'svelte-french-toast'
import store from "../../store"; import store from "../../store";
import Select from "svelte-select"; import Select from "svelte-select";
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte"; import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte"; import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte";
import Teams from "./Teams.svelte"; import Teams from "./Teams.svelte";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte"; import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
let [teamdata, original, delete_team, orgs, contacts, modal_open] = [ let [teamdata, original, delete_team, orgs, contacts, modal_open] = [
{}, {},
{}, {},
{}, {},
[], [],
[], [],
false, false,
]; ];
export let params; export let params;
export let import_modal_open = false; export let import_modal_open = false;
$: delete_triggered = false; $: delete_triggered = false;
$: save_enabled = data_changed && teamdata.parentGroup != null; $: save_enabled = data_changed && teamdata.parentGroup != null;
$: data_loaded = false; $: data_loaded = false;
$: data_changed = !(JSON.stringify(teamdata) === JSON.stringify(original)); $: data_changed = !(JSON.stringify(teamdata) === JSON.stringify(original));
$: sponsoring_contracts_show = true; $: sponsoring_contracts_show = true;
$: cards_show = true; $: cards_show = true;
$: certificates_show = true; $: certificates_show = true;
$: generate_teams = [original]; $: generate_teams = [original];
$: group = {}; $: group = {};
$: contact = {}; $: contact = {};
// //
const getContactLabel = (option) => const getContactLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const promise = RunnerTeamService.runnerTeamControllerGetOne( const promise = RunnerTeamService.runnerTeamControllerGetOne(
params.teamid params.teamid
).then((value) => { ).then((value) => {
data_loaded = true; data_loaded = true;
teamdata = Object.assign(teamdata, value); teamdata = Object.assign(teamdata, value);
original = Object.assign(original, value); original = Object.assign(original, value);
RunnerOrganizationService.runnerOrganizationControllerGetAll().then( RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => { (val) => {
orgs = val.map((r) => { orgs = val.map((r) => {
delete r.contact; delete r.contact;
r.teams = []; r.teams = [];
return { label: r.name, value: r }; return { label: r.name, value: r };
}); });
group = orgs.find((g) => g.value.id == teamdata.parentGroup.id); group = orgs.find((g) => g.value.id == teamdata.parentGroup.id);
} }
); );
GroupContactService.groupContactControllerGetAll().then((val) => { GroupContactService.groupContactControllerGetAll().then((val) => {
contacts = val.map((r) => { contacts = val.map((r) => {
return { label: getContactLabel(r), value: r }; return { label: getContactLabel(r), value: r };
}); });
if (teamdata.contact) { if (teamdata.contact) {
contact = contacts.find((g) => g.value.id == teamdata.contact.id); contact = contacts.find((g) => g.value.id == teamdata.contact.id);
} else { } else {
contact = null; contact = null;
} }
}); });
}); });
function deleteTeam() { function deleteTeam() {
RunnerTeamService.runnerTeamControllerRemove(original.id, false) RunnerTeamService.runnerTeamControllerRemove(original.id, false)
.then((resp) => { .then((resp) => {
toast($_("team-deleted")); toast($_("team-deleted"));
location.replace("./"); location.replace("./");
}) })
.catch((err) => { .catch((err) => {
modal_open = true; modal_open = true;
delete_team = original; delete_team = original;
}); });
} }
function submit() { function submit() {
if (data_loaded === true && save_enabled) { if (data_loaded === true && save_enabled) {
toast($_("updating-team")); toast($_("updating-team"));
let postdata = teamdata; let postdata = teamdata;
postdata.parentGroup = teamdata.parentGroup.id; postdata.parentGroup = teamdata.parentGroup.id;
postdata.contact = teamdata.contact?.id; postdata.contact = teamdata.contact?.id;
RunnerTeamService.runnerTeamControllerPut(original.id, postdata) RunnerTeamService.runnerTeamControllerPut(original.id, postdata)
.then((resp) => { .then((resp) => {
Object.assign(original, teamdata); Object.assign(original, teamdata);
original = original; original = original;
toast.success($_("updated-team")); toast.success($_("updated-team"));
}) })
.catch((err) => {}); .catch((err) => {});
} }
} }
</script> </script>
<ImportRunnerModal <ImportRunnerModal
current_runners={[]} current_runners={[]}
on:cancelDelete={(event) => { on:cancelDelete={(event) => {
import_modal_open = false; import_modal_open = false;
}} }}
passed_team={teamdata} passed_team={teamdata}
passed_orgs={[]} passed_orgs={[]}
passed_org={{}} passed_org={{}}
opened_from="TeamDetail" opened_from="TeamDetail"
bind:import_modal_open bind:import_modal_open
/> />
<ConfirmTeamDeletion bind:modal_open bind:delete_team /> <ConfirmTeamDeletion bind:modal_open bind:delete_team />
{#if data_loaded} {#if data_loaded}
<section class="container p-5"> <section class="container p-5">
<div class="flex flex-row mb-4"> <div class="mb-8 text-3xl font-extrabold leading-tight">
<div class="w-full"> {original.name}
<nav class="w-full flex"> <span data-id="org_actions_${teamdata.id}">
<ol class="list-none flex flex-row items-center justify-start"> <GenerateSponsoringContracts
<li class="flex items-center"> bind:sponsoring_contracts_show
<a class="mr-2" href="./" bind:generate_teams
><svg />
xmlns="http://www.w3.org/2000/svg" <GenerateRunnerCards bind:cards_show bind:generate_teams />
width="24" <GenerateRunnerCertificates
height="24" bind:certificates_show
viewBox="0 0 24 24" bind:generate_teams
fill="none" />
stroke="currentColor" {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:IMPORT")}
stroke-width="2" <button
stroke-linecap="round" on:click={() => {
stroke-linejoin="round" import_modal_open = true;
class="inline-block" }}
><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg 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"
{$_("teams")}</a >
> {$_("import-runners")}
</li> </button>
</ol> {/if}
</nav> {#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:DELETE")}
</div> {#if delete_triggered}
</div> <button
<div class="mb-4 text-3xl font-extrabold leading-tight"> on:click={deleteTeam}
{#if group} 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"
{group.label}{" > "} >{$_("confirm-delete")}</button
{/if} >
{original.name} [#{params.teamid}] <button
<span data-id="org_actions_${teamdata.id}"> on:click={() => {
<GenerateSponsoringContracts delete_triggered = !delete_triggered;
bind:sponsoring_contracts_show }}
bind:generate_teams 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
<GenerateRunnerCards bind:cards_show bind:generate_teams /> >
<GenerateRunnerCertificates {/if}
bind:certificates_show {#if !delete_triggered}
bind:generate_teams <button
/> on:click={() => {
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:IMPORT")} delete_triggered = true;
<button }}
on:click={() => { type="button"
import_modal_open = true; class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
}} >{$_("delete-team")}</button
type="button" >
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm" {/if}
> {/if}
{$_("import-runners")} {#if !delete_triggered}
</button> <button
{/if} on:click={submit}
{#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:DELETE")} disabled={!save_enabled}
{#if delete_triggered} class:opacity-50={!save_enabled}
<button type="button"
on:click={deleteTeam} 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"
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" >{$_("save-changes")}</button
>{$_("confirm-delete")}</button >
> {/if}
<button </span>
on:click={() => { </div>
delete_triggered = !delete_triggered; <div class="flex flex-row mb-4">
}} <div class="w-full">
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" <nav class="w-full flex">
>{$_("cancel")}</button <ol class="list-none flex flex-row items-center justify-start">
> <li class="mr-2 flex items-center">
{/if} <svg
{#if !delete_triggered} stroke="currentColor"
<button fill="none"
on:click={() => { stroke-width="2"
delete_triggered = true; viewBox="0 0 24 24"
}} stroke-linecap="round"
type="button" stroke-linejoin="round"
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" class="h-3 w-3 stroke-current"
>{$_("delete-team")}</button height="1em"
> width="1em"
{/if} xmlns="http://www.w3.org/2000/svg"
{/if} ><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
{#if !delete_triggered} <polyline points="9 22 9 12 15 12 15 22" /></svg
<button >
on:click={submit} </li>
disabled={!save_enabled} <li class="flex items-center">
class:opacity-50={!save_enabled} <a class="mr-2" href="/">Home</a><svg
type="button" stroke="currentColor"
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" fill="none"
>{$_("save-changes")}</button stroke-width="2"
> viewBox="0 0 24 24"
{/if} stroke-linecap="round"
</span> stroke-linejoin="round"
</div> class="h-3 w-3 mr-2 stroke-current"
<div class="text-sm w-full mt-2"> height="1em"
<label for="name" class="font-semibold text-gray-700">Name</label> width="1em"
<input xmlns="http://www.w3.org/2000/svg"
autocomplete="off" ><line x1="5" y1="12" x2="19" y2="12" />
placeholder="Name" <polyline points="12 5 19 12 12 19" /></svg
type="text" >
bind:value={teamdata.name} </li>
name="name" <li class="mr-2 flex items-center">
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" <svg
/> class="flex-shrink-0 w-5 h-5 mr-2"
</div> fill="currentColor"
<div class="text-sm w-full mt-2"> width="24"
<label for="contact" class="font-semibold text-gray-700" height="24"
>{$_("contact")}</label xmlns="http://www.w3.org/2000/svg"
> viewBox="0 0 640 512"
<Select ><path
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" fill="currentColor"
itemFilter={(label, filterText, option) => 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"
label.toLowerCase().includes(filterText.toLowerCase()) || /></svg
option.value.id.toString().startsWith(filterText.toLowerCase())} >
items={contacts} </li>
showChevron={true} <li class="flex items-center">
placeholder={$_("no-contact-selected")} <a class="mr-2" href="./">Teams</a><svg
noOptionsMessage={$_("no-contact-found")} stroke="currentColor"
bind:selectedValue={contact} fill="none"
on:select={(selectedValue) => stroke-width="2"
(teamdata.contact = selectedValue.detail.value)} viewBox="0 0 24 24"
on:clear={() => (teamdata.contact = null)} stroke-linecap="round"
/> stroke-linejoin="round"
</div> class="h-3 w-3 mr-2 stroke-current"
<div class="text-sm w-full mt-2"> height="1em"
<label for="org" class="font-semibold text-gray-700" width="1em"
>{$_("organization")}</label xmlns="http://www.w3.org/2000/svg"
> ><line x1="5" y1="12" x2="19" y2="12" />
<Select <polyline points="12 5 19 12 12 19" /></svg
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" >
itemFilter={(label, filterText, option) => </li>
label.toLowerCase().includes(filterText.toLowerCase()) || <li class="flex items-center">
option.id.value.toString().startsWith(filterText.toLowerCase())} <span class="mr-2">Team-Details #{params.teamid}</span>
items={orgs} </li>
showChevron={true} </ol>
placeholder={$_("search-for-an-organization-by-name-or-id")} </nav>
noOptionsMessage={$_("no-organizations-found")} </div>
bind:selectedValue={group} </div>
on:select={(selectedValue) => <div class="text-sm w-full">
(teamdata.parentGroup = selectedValue.detail.value)} <label for="name" class="font-medium text-gray-700">Name</label>
on:clear={() => (teamdata.parentGroup = null)} <input
/> autocomplete="off"
</div> placeholder="Name"
<div class="text-sm w-full mt-2"> type="text"
<span class="font-semibold text-gray-700">{$_("distance")}</span> bind:value={teamdata.name}
<br /> name="name"
<span class="text-gray-700" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
>{(original.total_distance / 1000).toFixed(2)} km</span />
> </div>
</div> <div class="text-sm w-full">
</section> <label for="contact" class="font-medium text-gray-700"
>{$_("contact")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase())}
items={contacts}
showChevron={true}
placeholder={$_("no-contact-selected")}
noOptionsMessage={$_("no-contact-found")}
bind:selectedValue={contact}
on:select={(selectedValue) =>
(teamdata.contact = selectedValue.detail.value)}
on:clear={() => (teamdata.contact = null)}
/>
</div>
<div class="text-sm w-full">
<label for="org" class="font-medium text-gray-700"
>{$_("organization")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.id.value.toString().startsWith(filterText.toLowerCase())}
items={orgs}
showChevron={true}
placeholder={$_("search-for-an-organization-by-name-or-id")}
noOptionsMessage={$_("no-organizations-found")}
bind:selectedValue={group}
on:select={(selectedValue) =>
(teamdata.parentGroup = selectedValue.detail.value)}
on:clear={() => (teamdata.parentGroup = null)}
/>
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_("distance")}</span>
<br />
<span class="text-gray-700"
>{(original.total_distance / 1000).toFixed(2)} km</span
>
</div>
</section>
{:else} {:else}
{#await promise} {#await promise}
{$_("team-detail-is-being-loaded")} {$_("team-detail-is-being-loaded")}
{:catch error} {:catch error}
<PromiseError /> <PromiseError />
{/await} {/await}
{/if} {/if}

View File

@ -8,7 +8,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("teams")} {$_("teams")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:CREATE")}

View File

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

View File

@ -139,7 +139,7 @@
bind:this={trackname_input} bind:this={trackname_input}
type="text" type="text"
name="trackname" name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-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} {#if isTracknameValid}
<span <span
@ -164,7 +164,7 @@
bind:value={tracklength} bind:value={tracklength}
type="number" type="number"
name="track_length_m" 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" 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" placeholder="1000"
/> />
<span <span
@ -195,7 +195,7 @@
bind:value={track_min_duration} bind:value={track_min_duration}
type="number" type="number"
name="track_min_duration" 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" 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} placeholder={smart_track_min_duration_placeholder}
/> />
<span <span

View File

@ -19,7 +19,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("tracks")} {$_("tracks")}
</h4> </h4>
<button <button
@ -101,7 +101,7 @@
<div class="ml-4"> <div class="ml-4">
{#if editTracks.findIndex((tr) => tr.id === t.id) !== -1} {#if editTracks.findIndex((tr) => tr.id === t.id) !== -1}
<input <input
class="shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-0.5" class="shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-0.5"
type="text" type="text"
value={t.name} value={t.name}
on:input={(e) => { on:input={(e) => {
@ -124,7 +124,7 @@
<div class="ml-4"> <div class="ml-4">
{#if editTracks.findIndex((tr) => tr.id === t.id) !== -1} {#if editTracks.findIndex((tr) => tr.id === t.id) !== -1}
<input <input
class="shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-0.5" class="shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-0.5"
type="number" type="number"
value={t.distance} value={t.distance}
on:input={(e) => { on:input={(e) => {
@ -147,7 +147,7 @@
<div class="ml-4"> <div class="ml-4">
{#if editTracks.findIndex((tr) => tr.id === t.id) !== -1} {#if editTracks.findIndex((tr) => tr.id === t.id) !== -1}
<input <input
class="shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-0.5" class="shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-0.5"
type="number" type="number"
value={t.minimumLapTime} value={t.minimumLapTime}
on:input={(e) => { on:input={(e) => {

View File

@ -156,7 +156,7 @@
bind:this={firstname_input} bind:this={firstname_input}
type="text" type="text"
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isFirstnameValid} {#if !isFirstnameValid}
<span <span
@ -179,7 +179,7 @@
bind:this={middlename_input} bind:this={middlename_input}
type="text" type="text"
name="trackname" name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> --> </div> -->
<div class="col-span-6"> <div class="col-span-6">
@ -198,7 +198,7 @@
bind:this={lastname_input} bind:this={lastname_input}
type="text" type="text"
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
{#if !isLastnameValid} {#if !isLastnameValid}
<span <span
@ -224,7 +224,7 @@
bind:this={password_input} bind:this={password_input}
type="password" type="password"
name="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-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"
/> />
<PasswordStrength <PasswordStrength
bind:password_change={password_input_value} bind:password_change={password_input_value}
@ -243,7 +243,7 @@
bind:this={username_input} bind:this={username_input}
type="text" type="text"
name="trackname" name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
@ -259,7 +259,7 @@
bind:this={email_input} bind:this={email_input}
type="email" type="email"
name="email" name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-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 !isEmailValid} {#if !isEmailValid}
<span <span

View File

@ -1,217 +1,235 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import store from "../../store"; import store from "../../store";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
import { UserService, UserGroupService } from "@odit/lfk-client-js"; import { UserService, UserGroupService } from "@odit/lfk-client-js";
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import toast from "svelte-french-toast"; import toast from "svelte-french-toast";
export let params; export let params;
const user_promise = UserService.userControllerGetOne(params.userid); const user_promise = UserService.userControllerGetOne(params.userid);
let data_loaded = false; let data_loaded = false;
let usergroups_array_original = []; let usergroups_array_original = [];
const colors = [ const colors = [
"#f3558e", "#f3558e",
"#17b978", "#17b978",
"#3498db", "#3498db",
"#3f3b3b", "#3f3b3b",
"#775ada", "#775ada",
"#7ed6df_#000000", "#7ed6df_#000000",
"#000000", "#000000",
"#21e6c1_#000000", "#21e6c1_#000000",
"#c0392b", "#c0392b",
"#d35400", "#d35400",
"#7f8c8d", "#7f8c8d",
"#6ab04c", "#6ab04c",
"#4834d4", "#4834d4",
"#ff1f5a", "#ff1f5a",
"#eac100", "#eac100",
]; ];
let matched_colors = []; let matched_colors = [];
$: delete_triggered = false; $: delete_triggered = false;
$: original_data = {}; $: original_data = {};
$: editable_userdata = {}; $: editable_userdata = {};
$: allgroups = []; $: allgroups = [];
$: allgroups_ids = []; $: allgroups_ids = [];
$: usergroups_array = []; $: usergroups_array = [];
$: search_permission = ""; $: search_permission = "";
user_promise.then((data) => { user_promise.then((data) => {
let current_target = ""; let current_target = "";
let colorindex = -1; let colorindex = -1;
// alphabetically sort permissions for color compatibility for target // alphabetically sort permissions for color compatibility for target
data.permissions = data.permissions.sort(); data.permissions = data.permissions.sort();
data.permissions.forEach((p) => { data.permissions.forEach((p) => {
const target = p.split(":")[0]; const target = p.split(":")[0];
if (current_target !== p.split(":")[0]) { if (current_target !== p.split(":")[0]) {
colorindex++; colorindex++;
current_target = p.split(":")[0]; current_target = p.split(":")[0];
} }
let background = colors[colorindex]; let background = colors[colorindex];
let foreground = "#fff"; let foreground = "#fff";
if (background.includes("_")) { if (background.includes("_")) {
foreground = background.split("_")[1]; foreground = background.split("_")[1];
background = background.split("_")[0]; background = background.split("_")[0];
} }
matched_colors[target] = [background, foreground]; matched_colors[target] = [background, foreground];
}); });
// //
data_loaded = true; data_loaded = true;
original_data = Object.assign(original_data, data); original_data = Object.assign(original_data, data);
editable_userdata = data; editable_userdata = data;
data.groups.forEach((g) => { data.groups.forEach((g) => {
usergroups_array = usergroups_array.concat([g.id]); usergroups_array = usergroups_array.concat([g.id]);
}); });
usergroups_array_original = usergroups_array; usergroups_array_original = usergroups_array;
allgroups.forEach((g) => { allgroups.forEach((g) => {
allgroups_ids.push(g.id); allgroups_ids.push(g.id);
}); });
}); });
UserGroupService.userGroupControllerGetAll().then((data) => { UserGroupService.userGroupControllerGetAll().then((data) => {
allgroups = data; allgroups = data;
}); });
$: changes_performed = !( $: changes_performed = !(
JSON.stringify(original_data) == JSON.stringify(editable_userdata) JSON.stringify(original_data) == JSON.stringify(editable_userdata)
); );
$: groups_changed = $: groups_changed =
JSON.stringify(usergroups_array) === JSON.stringify(usergroups_array) ===
JSON.stringify(usergroups_array_original); JSON.stringify(usergroups_array_original);
$: save_enabled = $: save_enabled =
(changes_performed || !groups_changed) && isEmail(editable_userdata.email); (changes_performed || !groups_changed) && isEmail(editable_userdata.email);
function submit() { function submit() {
if (data_loaded === true && save_enabled) { if (data_loaded === true && save_enabled) {
editable_userdata.groups = usergroups_array; editable_userdata.groups = usergroups_array;
toast.loading($_("updating-user")); toast.loading($_("updating-user"));
UserService.userControllerPut(original_data.id, editable_userdata) UserService.userControllerPut(original_data.id, editable_userdata)
.then((resp) => { .then((resp) => {
Object.assign(original_data, resp); Object.assign(original_data, resp);
Object.assign(editable_userdata, resp); Object.assign(editable_userdata, resp);
original_data.permissions = resp.permissions; original_data.permissions = resp.permissions;
usergroups_array = []; usergroups_array = [];
resp.groups.forEach((g) => { resp.groups.forEach((g) => {
usergroups_array = usergroups_array.concat([g.id]); usergroups_array = usergroups_array.concat([g.id]);
}); });
usergroups_array_original = usergroups_array; usergroups_array_original = usergroups_array;
// //
toast.dismiss(); toast.dismiss();
toast($_("user-updated")); toast($_("user-updated"));
}) })
.catch((err) => {}); .catch((err) => {});
} }
} }
function deleteUser() { function deleteUser() {
UserService.userControllerRemove(original_data.id, true) UserService.userControllerRemove(original_data.id, true)
.then((resp) => { .then((resp) => {
location.replace("./"); location.replace("./");
}) })
.catch((err) => {}); .catch((err) => {});
} }
</script> </script>
{#await user_promise} {#await user_promise}
<!-- --> <!-- -->
{:then user} {:then user}
<section class="container p-5 select-none"> <section class="container p-5 select-none">
<div class="flex flex-row mb-4"> <div class="flex flex-row mb-4">
<div class="w-full"> <div class="w-full">
<nav class="w-full flex"> <nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start"> <ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="./" <svg
><svg class="flex-shrink-0 w-5 h-5 mr-2"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24"
stroke="currentColor" ><path
stroke-width="2" fill="currentColor"
stroke-linecap="round" 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"
stroke-linejoin="round" /></svg
class="inline-block" >
><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg </li>
> <li class="flex items-center">
{$_("users")}</a <a class="mr-2" href="./">{$_("users")}</a><svg
> stroke="currentColor"
</li> fill="none"
</ol> stroke-width="2"
</nav> viewBox="0 0 24 24"
</div> stroke-linecap="round"
</div> stroke-linejoin="round"
<div class="mb-4 text-3xl font-extrabold"> class="h-3 w-3 mr-2 stroke-current"
{original_data.firstname} height="1em"
{original_data.lastname} width="1em"
<span data-id="user_actions_${editable_userdata.id}"> xmlns="http://www.w3.org/2000/svg"
{#if store.state.jwtinfo.userdetails.permissions.includes("USER:DELETE")} ><line x1="5" y1="12" x2="19" y2="12" />
{#if delete_triggered} <polyline points="12 5 19 12 12 19" /></svg
<button >
on:click={deleteUser} </li>
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" <li class="flex items-center">
>{$_("confirm-delete")}</button <span class="mr-2"
> >{original_data.firstname}
<button {original_data.lastname}</span
on:click={() => { >
delete_triggered = !delete_triggered; </li>
}} </ol>
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" </nav>
>{$_("cancel")}</button </div>
> </div>
{/if} <div class="mb-8 text-3xl font-extrabold">
{#if !delete_triggered} {original_data.firstname}
<button {original_data.lastname}
on:click={() => { <span data-id="user_actions_${editable_userdata.id}">
delete_triggered = true; {#if store.state.jwtinfo.userdetails.permissions.includes("USER:DELETE")}
}} {#if delete_triggered}
type="button" <button
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm" on:click={deleteUser}
>{$_("delete-user")}</button class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm"
> >{$_("confirm-delete")}</button
{/if} >
{/if} <button
{#if !delete_triggered} on:click={() => {
<button delete_triggered = !delete_triggered;
disabled={!save_enabled} }}
class:opacity-50={!save_enabled} 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"
type="button" >{$_("cancel")}</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" {/if}
>{$_("save-changes")}</button {#if !delete_triggered}
> <button
{/if} on:click={() => {
</span> delete_triggered = true;
</div> }}
<div class="mt-3 text-sm w-full"> type="button"
<label for="enabled" class="font-semibold text-gray-700" 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"
>{$_("active")}?</label >{$_("delete-user")}</button
> >
<br /> {/if}
<p class="text-gray-500"> {/if}
<input {#if !delete_triggered}
id="enabled" <button
on:change={() => { disabled={!save_enabled}
editable_userdata.enabled = !editable_userdata.enabled; class:opacity-50={!save_enabled}
}} type="button"
name="enabled" on:click={submit}
type="checkbox" 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"
checked={editable_userdata.enabled} >{$_("save-changes")}</button
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" >
/> {/if}
{$_("set-the-user-active-inactive")} </span>
</p> </div>
</div> <div class="mt-3 text-sm w-full">
<div class="text-sm w-full mt-2"> <label for="enabled" class="ml-1 font-medium text-gray-700"
<label for="firstname" class="font-semibold text-gray-700" >{$_("active")}?</label
>{$_("first-name")}</label >
> <br />
<input <p class="text-gray-500">
autocomplete="off" <input
placeholder={$_("first-name")} id="enabled"
type="text" on:change={() => {
bind:value={editable_userdata.firstname} editable_userdata.enabled = !editable_userdata.enabled;
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" name="enabled"
/> type="checkbox"
</div> checked={editable_userdata.enabled}
<!-- <div class="text-sm w-full mt-2"> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
<label for="middlename" class="font-semibold text-gray-700" />
{$_("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"
bind:value={editable_userdata.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
</div>
<!-- <div class="text-sm w-full">
<label for="middlename" class="font-medium text-gray-700"
>{$_("middle-name")}</label >{$_("middle-name")}</label
> >
<input <input
@ -220,102 +238,102 @@
type="text" type="text"
bind:value={editable_userdata.middlename} bind:value={editable_userdata.middlename}
name="middlename" name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> --> </div> -->
<div class="text-sm w-full mt-2"> <div class="text-sm w-full">
<label for="lastname" class="font-semibold text-gray-700" <label for="lastname" class="font-medium text-gray-700"
>{$_("last-name")}</label >{$_("last-name")}</label
> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_("last-name")} placeholder={$_("last-name")}
type="text" type="text"
bind:value={editable_userdata.lastname} bind:value={editable_userdata.lastname}
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="text-sm w-full mt-2"> <div class="text-sm w-full">
<label for="email" class="font-semibold text-gray-700" <label for="email" class="font-medium text-gray-700"
>{$_("e-mail-adress")}</label >{$_("e-mail-adress")}</label
> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_("e-mail-adress")} placeholder={$_("e-mail-adress")}
type="email" type="email"
bind:value={editable_userdata.email} bind:value={editable_userdata.email}
name="email" name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
{#if !isEmail(editable_userdata.email)} {#if !isEmail(editable_userdata.email)}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1" class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>{$_("valid-email-is-required")}</span >{$_("valid-email-is-required")}</span
> >
{/if} {/if}
<div class="text-sm w-full mt-2"> <div class="text-sm w-full">
<label for="username" class="font-semibold text-gray-700" <label for="username" class="font-medium text-gray-700"
>{$_("username")}</label >{$_("username")}</label
> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_("username")} placeholder={$_("username")}
type="text" type="text"
bind:value={editable_userdata.username} bind:value={editable_userdata.username}
name="username" name="username"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
<div class="text-sm w-full mt-2"> <div class="text-sm w-full">
<span class="font-semibold">{$_("groups")}</span> <span class="font-medium">{$_("groups")}</span>
<!-- svelte-ignore a11y-no-onchange --> <!-- svelte-ignore a11y-no-onchange -->
<select <select
bind:value={usergroups_array} bind:value={usergroups_array}
class="mt-1 focus:ring-indigo-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"
multiple multiple
> >
{#each allgroups as g} {#each allgroups as g}
{#if usergroups_array.includes(g.id)} {#if usergroups_array.includes(g.id)}
<option selected value={g.id}>{g.name}</option> <option selected value={g.id}>{g.name}</option>
{:else} {:else}
<option value={g.id}>{g.name}</option> <option value={g.id}>{g.name}</option>
{/if} {/if}
{/each} {/each}
</select> </select>
</div> </div>
<div class="text-sm w-full mt-8"> <div class="text-sm w-full mt-8">
<p class="font-medium mb-4"> <p class="font-medium mb-4">
{$_("permissions")} {$_("permissions")}
<a <a
class="px-4 py-2 bg-gray-500 rounded-md text-white" class="px-4 py-2 bg-gray-500 rounded-md text-white"
href="/users/{params.userid}/permissions/">{$_("edit-permissions")}</a href="/users/{params.userid}/permissions/">{$_("edit-permissions")}</a
> >
</p> </p>
<div class="w-full sm:my-px sm:px-px sm:w-1/2"> <div class="w-full sm:my-px sm:px-px sm:w-1/2">
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_("search-for-permission")} placeholder={$_("search-for-permission")}
type="text" type="text"
bind:value={search_permission} bind:value={search_permission}
class="mt-4 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" class="mt-4 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/> />
</div> </div>
{#each original_data.permissions as p} {#each original_data.permissions as p}
{#if p.toLowerCase().includes(search_permission.toLowerCase())} {#if p.toLowerCase().includes(search_permission.toLowerCase())}
<span <span
style="background:{matched_colors[ style="background:{matched_colors[
p.split(':')[0] p.split(':')[0]
][0]};color:{matched_colors[p.split(':')[0]][1]};" ][0]};color:{matched_colors[p.split(':')[0]][1]};"
class="mt-1 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-indigo-100 rounded" class="mt-1 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-indigo-100 rounded"
>{p}</span >{p}</span
> >
<!-- --> <!-- -->
{/if} {/if}
{/each} {/each}
</div> </div>
</section> </section>
{:catch error} {:catch error}
<PromiseError {error} /> <PromiseError {error} />
{/await} {/await}

View File

@ -138,7 +138,7 @@
</nav> </nav>
</div> </div>
</div> </div>
<div class="mb-4 text-3xl font-extrabold"> <div class="mb-8 text-3xl font-extrabold">
{$_("permissions")}: {$_("permissions")}:
{original_data.firstname} {original_data.firstname}
{original_data.middlename || ""} {original_data.middlename || ""}

View File

@ -8,7 +8,7 @@
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight font-mono">
{$_("users")} {$_("users")}
</h4> </h4>
{#if store.state.jwtinfo.userdetails.permissions.includes("USER:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("USER:CREATE")}

View File

@ -115,11 +115,11 @@
> >
{/if} {/if}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 gap-0.5 flex flex-wrap"> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{#each u.groups as g} {#each u.groups as g}
<a <a
href="../groups/{g.id}" href="../groups/{g.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border bg-gray-100 text-gray-800 border-current" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full border bg-gray-100 text-gray-800"
>{g.name}</a >{g.name}</a
> >
{/each} {/each}

View File

@ -1,11 +1,5 @@
import "./style.css"; import "./style.css";
import App from "./App.svelte"; import App from "./App.svelte";
import "@fontsource/athiti/200.css";
import "@fontsource/athiti/300.css";
import "@fontsource/athiti/400.css";
import "@fontsource/athiti/500.css";
import "@fontsource/athiti/600.css";
import "@fontsource/athiti/700.css";
const app = new App({ const app = new App({
target: document.body, target: document.body,

View File

@ -1,10 +1,7 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
.activenav { .activenav {
@apply bg-gray-300; @apply bg-gray-300;
@apply text-black; @apply text-black;
}
* {
font-family: Athiti;
} }
@tailwind utilities; @tailwind utilities;

View File

@ -1,10 +1,10 @@
import fs from "fs"; import fs from "fs";
const packagejson = JSON.parse( const packagejson = JSON.parse(
fs.readFileSync(`./package.json`, { encoding: "utf-8" }), fs.readFileSync(`./package.json`, { encoding: "utf-8" })
); );
const original = fs.readFileSync(`./index.html`, { encoding: "utf-8" }); const original = fs.readFileSync(`./index.html`, { encoding: "utf-8" });
let out = original.replace( let out = original.replace(
/RELEASE_INFO-(\S)+-RELEASE_INFO/gi, /RELEASE_INFO-(\S)+-RELEASE_INFO/gi,
"RELEASE_INFO-" + packagejson.version + "-RELEASE_INFO", "RELEASE_INFO-" + packagejson.version + "-RELEASE_INFO"
); );
fs.writeFileSync(`./index.html`, out); fs.writeFileSync(`./index.html`, out);