Compare commits

..

69 Commits

Author SHA1 Message Date
845737ee8e Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 16:18:55 +02:00
6993511c67 feat(RunnersOverview): row selection 2023-04-12 16:18:45 +02:00
9111ad147c Updated group name conversion 2023-04-12 16:14:06 +02:00
333214aa8f Added distance conversion 2023-04-12 16:09:57 +02:00
c0cde02fec Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 16:06:58 +02:00
070a20a2e5 Table header i18n 2023-04-12 16:06:55 +02:00
c5e8409079 add basic table styling 2023-04-12 16:05:42 +02:00
67eff0eda9 Dsable column filter for distance 2023-04-12 16:03:53 +02:00
3de7b632e0 Moved sort onclick to header only 2023-04-12 16:03:20 +02:00
842248e4c4 Added sort 2023-04-12 16:02:00 +02:00
c5d7ec25b5 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 15:58:33 +02:00
a9a965d698 Added filterfunctions 2023-04-12 15:58:31 +02:00
dc866dd540 default to 50 rows pagination 2023-04-12 15:58:13 +02:00
89252619b1 Yeeted old datatable references 2023-04-12 15:56:53 +02:00
2699b06d7c Filter id as equals 2023-04-12 15:54:36 +02:00
fd0d45f721 debug table 2023-04-12 15:52:19 +02:00
5ecf838dd2 cleanup headers 2023-04-12 15:52:13 +02:00
45a7a90cb8 pagination size 2023-04-12 15:52:05 +02:00
cac851f2b1 fix pagination next prev 2023-04-12 15:51:46 +02:00
238082b657 getPaginationRowModel 2023-04-12 15:48:08 +02:00
aecbabe522 -> onkeyup 2023-04-12 15:43:52 +02:00
a9cdac4f74 Added filter 2023-04-12 15:40:43 +02:00
a59dbbe50e wip: pagination 2023-04-12 15:36:45 +02:00
9bec95ede8 dependency update, drop focusTrap, first tanstack experiment in RunnersOverview 2023-04-12 15:20:38 +02:00
70307a9e82 Added delete cards button
All checks were successful
continuous-integration/drone/push Build is passing
ref #161
2023-04-12 14:34:06 +02:00
ef077b4e6a Cards deleted migrations
ref #161
2023-04-12 14:32:55 +02:00
dcabed4e93 Added translations
ref #161
2023-04-12 14:30:35 +02:00
1af047f66e Moved filter function to typed version
All checks were successful
continuous-integration/drone/push Build is passing
closes #171
2023-04-12 14:16:36 +02:00
ee91748b3c Bumped pnpm to 8
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-29 19:50:52 +02:00
e5241d619b No longer switching pnpm store path
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-29 19:48:02 +02:00
d79608edbb Switched drone to pnpm cache
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-29 19:46:49 +02:00
4cbd26580e Moved docker to pnpm with cache 2023-03-29 19:45:35 +02:00
fe62ad5539 ignore pnpm store from now on
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-29 19:44:06 +02:00
eb13f038a1 Removed pnpm store 2023-03-29 19:43:16 +02:00
9505c2b030 add TODO for ScansOverview status filter
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-19 12:45:07 +01:00
008835c24f ScansOverview: add ThFilterTrack
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-19 12:38:09 +01:00
7083b3d8d2 card/ThFilterRunner: text align left 2023-03-19 12:29:23 +01:00
754931b2f6 ScansOverview: migrate to datatable
All checks were successful
continuous-integration/drone/push Build is passing
close #168
2023-03-19 12:28:27 +01:00
2dc8ffba32 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 15:05:29 +01:00
d0fe6a2e85 new license file version [CI SKIP] 2023-03-15 14:05:18 +00:00
85705b6e68 🚀RELEASE v0.17.3 2023-03-15 15:05:14 +01:00
3ea7a015a9 dependency fixes
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 15:04:55 +01:00
44329413ed set pnpm to @7 2023-03-15 15:02:54 +01:00
46db68ab22 🚀RELEASE v0.17.2 2023-03-15 14:53:48 +01:00
dc9d7f22a2 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 14:47:32 +01:00
f917018fd9 improved ThFilterGroup style 2023-03-15 14:47:27 +01:00
7b420c430d 🚀RELEASE v0.17.1 2023-03-15 14:44:26 +01:00
00359d25c1 new license file version [CI SKIP] 2023-03-15 13:43:57 +00:00
d8a3063735 Revert "package dependency fixes, bumps, lockfile update"
Some checks failed
continuous-integration/drone/push Build is failing
This reverts commit 2c73b9862d.
2023-03-15 14:43:33 +01:00
6491af19e3 🚀RELEASE v0.17.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 13:35:48 +01:00
61328d20ed new license file version [CI SKIP] 2023-03-15 12:34:41 +00:00
0a6d92a1f3 Switched license generation to cache registry and pnpm
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 13:34:14 +01:00
3a576d1073 Pinned pnpm in dockerfile, thx @philipp
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 13:29:33 +01:00
b30b98b521 Pinned ci node version 2023-03-15 13:25:59 +01:00
43d82a2af0 Fixed pnpm being called without being installed
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 13:18:07 +01:00
6a4495b813 Merge pull request 'bugfix/162-create_card_modal' (#163) from bugfix/162-create_card_modal into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #163
2023-03-15 12:15:24 +00:00
e8a0ad6647 You can now create cards from runners by searching via #runnerid
ref #162
2023-03-15 13:12:42 +01:00
92b89cc4d8 Fixed double space in label
ref #162
2023-03-15 13:09:07 +01:00
268b1b1d98 Removed unused log
ref #162
2023-03-15 13:07:49 +01:00
75bc89ca30 Fixed runners not showing up
ref #162
2023-03-15 13:06:56 +01:00
0625937068 Merge pull request 'filter by runner full names + "#<ID>"' (#160) from feature/159-cardsoverview-filter-for-runner-full-names-and-id into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #160
2023-03-15 11:20:05 +00:00
32a9074963 Wow this api is fun
ref #159
2023-03-15 12:14:39 +01:00
b869b5fd2a remodelled for early return
ref #159
2023-03-15 11:39:51 +01:00
3a3e2f7157 add ThFilterRunner
ref #159
2023-03-15 11:10:08 +01:00
bea57aa03a fix styling for table filters th border
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 11:08:05 +01:00
30991d5364 UsersOverview: drop pfp
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:53:16 +01:00
5cc8b0811c UsersOverview: change profilepic scaling
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:51:40 +01:00
2c73b9862d package dependency fixes, bumps, lockfile update
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:51:23 +01:00
732b2f061e wip: pnpm + node version 2023-03-14 09:29:27 +01:00
50 changed files with 5826 additions and 1366 deletions

View File

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

View File

@@ -19,6 +19,13 @@ get:
path: odit-git-bot
name: sshkey
---
kind: secret
name: npm_url
get:
path: odit-npm-cache
name: url
---
kind: pipeline
type: kubernetes
@@ -27,10 +34,14 @@ name: build:dev
steps:
- name: run full license export
depends_on: ["clone"]
image: registry.odit.services/hub/library/node:alpine
image: registry.odit.services/hub/library/node:19.7.0-alpine3.16
commands:
- yarn
- yarn licenses:export
- npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8
- pnpm i
- pnpm licenses:export
environment:
NPM_REGISTRY_URL:
from_secret: npm_url
- name: push new licenses file to repo
depends_on: ["run full license export"]
image: appleboy/drone-git-push
@@ -51,10 +62,8 @@ steps:
password:
from_secret: docker_password
build_args:
- NPM_REGISTRY_DOMAIN:
from_secret: npmjs_domain
- NPM_REGISTRY_TOKEN:
from_secret: npmjs_token
- NPM_REGISTRY_URL:
from_secret: npm_url
repo: lfk/frontend
tags:
- dev
@@ -80,10 +89,8 @@ steps:
password:
from_secret: docker_password
build_args:
- NPM_REGISTRY_DOMAIN:
from_secret: npmjs_domain
- NPM_REGISTRY_TOKEN:
from_secret: npmjs_token
- NPM_REGISTRY_URL:
from_secret: npm_url
repo: lfk/frontend
tags:
- "${DRONE_TAG}"

7
.gitignore vendored
View File

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

View File

@@ -2,8 +2,56 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [0.17.3](https://git.odit.services/lfk/frontend/compare/0.17.2...0.17.3)
- dependency fixes [`3ea7a01`](https://git.odit.services/lfk/frontend/commit/3ea7a015a9beba3c2e4d3eb966f24ff6d4ac786e)
- set pnpm to @7 [`4432941`](https://git.odit.services/lfk/frontend/commit/44329413ed2ca23f74e86db041b2c25b2b1c2a2b)
#### [0.17.2](https://git.odit.services/lfk/frontend/compare/0.17.1...0.17.2)
> 15 March 2023
- new license file version [CI SKIP] [`00359d2`](https://git.odit.services/lfk/frontend/commit/00359d25c1bd3efdd6365bf284b3c07634049399)
- 🚀RELEASE v0.17.2 [`46db68a`](https://git.odit.services/lfk/frontend/commit/46db68ab229dc740dfff8835ef916f2c2e629b27)
- improved ThFilterGroup style [`f917018`](https://git.odit.services/lfk/frontend/commit/f917018fd92a8a5b034f735ac8b6e41995044317)
#### [0.17.1](https://git.odit.services/lfk/frontend/compare/0.17.0...0.17.1)
> 15 March 2023
- Revert "package dependency fixes, bumps, lockfile update" [`d8a3063`](https://git.odit.services/lfk/frontend/commit/d8a30637351e164599e07a6473d9a1d2b08d245d)
- 🚀RELEASE v0.17.1 [`7b420c4`](https://git.odit.services/lfk/frontend/commit/7b420c430d27bf0fc85a4297780164a593fc1be3)
#### [0.17.0](https://git.odit.services/lfk/frontend/compare/0.16.5...0.17.0)
> 15 March 2023
- new license file version [CI SKIP] [`61328d2`](https://git.odit.services/lfk/frontend/commit/61328d20ed2cfd1df7d3c32767f9c64154879d6d)
- wip: pnpm + node version [`732b2f0`](https://git.odit.services/lfk/frontend/commit/732b2f061e465bd08cf34c58d8cd6b021ba25dce)
- package dependency fixes, bumps, lockfile update [`2c73b98`](https://git.odit.services/lfk/frontend/commit/2c73b9862d401dac15021eed3f7847d46132a8ed)
- Fixed runners not showing up [`75bc89c`](https://git.odit.services/lfk/frontend/commit/75bc89ca3020c48f490c7602374616bd9461e78f)
- add ThFilterRunner [`3a3e2f7`](https://git.odit.services/lfk/frontend/commit/3a3e2f71575d3a0e39a5e13b05cff932b8683ac9)
- fix styling for table filters th border [`bea57aa`](https://git.odit.services/lfk/frontend/commit/bea57aa03acaaaa4b1860b30228dd5d1298317f8)
- You can now create cards from runners by searching via #runnerid [`e8a0ad6`](https://git.odit.services/lfk/frontend/commit/e8a0ad6647ab39252865f62b755f27c34ac2d243)
- remodelled for early return [`b869b5f`](https://git.odit.services/lfk/frontend/commit/b869b5fd2a01955fb21f936fa38eb5a9648e7de3)
- 🚀RELEASE v0.17.0 [`6491af1`](https://git.odit.services/lfk/frontend/commit/6491af19e375cbeba7ddd94e463b4dfe308a70a8)
- Wow this api is fun [`32a9074`](https://git.odit.services/lfk/frontend/commit/32a9074963cce3328f14b1f981ddd5ee49df0008)
- Fixed double space in label [`92b89cc`](https://git.odit.services/lfk/frontend/commit/92b89cc4d88c9d5625c2ddf7c81c98494f7f5271)
- UsersOverview: drop pfp [`30991d5`](https://git.odit.services/lfk/frontend/commit/30991d5364a09d517b2115a7e9ea3fbf1fe2e57d)
- Switched license generation to cache registry and pnpm [`0a6d92a`](https://git.odit.services/lfk/frontend/commit/0a6d92a1f3c5562f11562c433b3a04e3eaae3da4)
- Pinned pnpm in dockerfile, thx @philipp [`3a576d1`](https://git.odit.services/lfk/frontend/commit/3a576d1073ee503b68100e01054a1756bab62805)
- Pinned ci node version [`b30b98b`](https://git.odit.services/lfk/frontend/commit/b30b98b521eda2bc7fc055097546f716e90d92ef)
- Fixed pnpm being called without being installed [`43d82a2`](https://git.odit.services/lfk/frontend/commit/43d82a2af04af49c2169f78a0d0f27ef7e4d7558)
- Merge pull request 'bugfix/162-create_card_modal' (#163) from bugfix/162-create_card_modal into dev [`6a4495b`](https://git.odit.services/lfk/frontend/commit/6a4495b8131a31cd48a608c2275e80494d0a0fb4)
- Removed unused log [`268b1b1`](https://git.odit.services/lfk/frontend/commit/268b1b1d9830de196d1d95345d7a2467bbf19eb6)
- Merge pull request 'filter by runner full names + "#&lt;ID&gt;"' (#160) from feature/159-cardsoverview-filter-for-runner-full-names-and-id into dev [`0625937`](https://git.odit.services/lfk/frontend/commit/0625937068f0786078ffd29b9c8bb54949350b6c)
- UsersOverview: change profilepic scaling [`5cc8b08`](https://git.odit.services/lfk/frontend/commit/5cc8b0811cf290f97a4399b23c5ea4d961a5a91c)
#### [0.16.5](https://git.odit.services/lfk/frontend/compare/0.16.4...0.16.5)
> 14 March 2023
- 🚀RELEASE v0.16.5 [`3680533`](https://git.odit.services/lfk/frontend/commit/3680533eefef042fc77246dd3d374aafe10c428f)
- new license file version [CI SKIP] [`405dfa0`](https://git.odit.services/lfk/frontend/commit/405dfa0c34ba87fc450c22e0e9974f92c4cdeffe)
#### [0.16.4](https://git.odit.services/lfk/frontend/compare/0.16.3...0.16.4)

View File

@@ -1,11 +1,14 @@
FROM registry.odit.services/hub/library/node:19.5.0-alpine3.16 as build
FROM registry.odit.services/hub/library/node:19.7.0-alpine3.16 as build
ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app
COPY package.json ./
RUN npx pnpm i
COPY package.json *.config.js postcss.config.cjs tailwind.config.js vite.config.js index.html ./
COPY package.json pnpm-lock.yaml *.config.js *.config.cjs index.html ./
RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8 && pnpm i
COPY src ./src
COPY public ./public
RUN yarn build
RUN pnpm build
# final image
FROM registry.odit.services/library/nginx-brotli:3.15 as final
COPY --from=build /app/dist /usr/share/nginx/html

View File

@@ -13,7 +13,7 @@
</head>
<body>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.16.5-RELEASE_INFO</span>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.17.3-RELEASE_INFO</span>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script>
<script type="module" src="/src/main.js"></script>

View File

@@ -1,6 +1,7 @@
{
"name": "@odit/lfk-frontend",
"version": "0.16.5",
"version": "0.17.3",
"type": "module",
"scripts": {
"i18n-order": "node order.js",
"dev": "vite",
@@ -10,32 +11,17 @@
},
"license": "CC-BY-NC-SA-4.0",
"devDependencies": {
"@odit/lfk-client-js": "0.13.1",
"@odit/license-exporter": "0.0.11",
"@sveltejs/vite-plugin-svelte": "1.0.0-next.6",
"@types/html-minifier": "4.0.0",
"@vincjo/datatables": "^1.1.0",
"auto-changelog": "2.2.1",
"autoprefixer": "10.2.5",
"check-password-strength": "2.0.2",
"csvtojson": "2.0.10",
"gridjs": "3.4.0",
"@odit/license-exporter": "0.0.12",
"@sveltejs/vite-plugin-svelte": "2.0.4",
"@types/html-minifier": "4.0.2",
"auto-changelog": "2.4.0",
"autoprefixer": "10.4.14",
"html-minifier": "4.0.0",
"localforage": "1.9.0",
"marked": "2.0.3",
"postcss": "8.2.10",
"release-it": "14.6.1",
"svelte": "3.37.0",
"svelte-focus-trap": "1.2.0",
"svelte-i18n": "3.3.9",
"svelte-preprocess": "4.7.0",
"postcss": "8.4.21",
"release-it": "15.10.1",
"svelte-select": "3.17.0",
"tailwindcss": "3.2.7",
"tinro": "0.6.1",
"toastify-js": "1.10.0",
"validator": "13.5.2",
"vite": "2.1.5",
"xlsx": "0.16.9"
"tailwindcss": "3.3.1",
"vite": "4.2.1"
},
"release-it": {
"git": {
@@ -55,6 +41,24 @@
}
},
"dependencies": {
"@paralleldrive/cuid2": "^2.2.0"
"@odit/lfk-client-js": "0.14.3",
"@paralleldrive/cuid2": "^2.2.0",
"@tanstack/svelte-table": "^8.8.5",
"@tanstack/table-core": "^8.8.5",
"@vincjo/datatables": "^1.5.2",
"check-password-strength": "2.0.7",
"csvtojson": "2.0.10",
"gridjs": "3.4.0",
"localforage": "1.10.0",
"marked": "2.0.3",
"svelte": "3.58.0",
"svelte-i18n": "3.6.0",
"tinro": "0.6.12",
"toastify-js": "1.12.0",
"validator": "13.9.0",
"xlsx": "0.18.5"
},
"volta": {
"node": "19.7.0"
}
}

4047
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerCardService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let bulk_modal_open;
@@ -133,7 +133,7 @@
{#if bulk_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
bulk_modal_open = false;

View File

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

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerCardService, RunnerService } from "@odit/lfk-client-js";
import Select from "svelte-select";
import Toastify from "toastify-js";
@@ -13,9 +13,16 @@
export let original_data = {};
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) =>
const filterRunners = (label, filterText, option) => {
if (filterText.startsWith("#")) {
return option.value.id == parseInt(filterText.replace("#",""))
}
return (
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase());
option.value.toString().startsWith(filterText.toLowerCase())
);
};
function focus(el) {
el.focus();
}
@@ -87,7 +94,7 @@
{#if edit_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
edit_modal_open = false;

View File

@@ -8,6 +8,7 @@
import CardDetailModal from "./CardDetailModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import ThFilterStatus from "./ThFilterStatus.svelte";
import ThFilterRunner from "./ThFilterRunner.svelte";
export let edit_modal_open = false;
export let runner = {};
export let editable = {};
@@ -54,7 +55,7 @@
bind:runner
bind:editable
bind:original_data
on:dataUpdated={(handler.setRows(current_cards))}
on:dataUpdated={handler.setRows(current_cards)}
/>
{/if}
@@ -71,14 +72,53 @@
{#if current_cards.length === 0}
<CardsEmptyState />
{:else}
<div class="h-12">
<div class="h-12 mt-1">
{#if cards_show}
<button
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
on:click={async () => {
const prom = [];
for (const card of generate_cards) {
prom.push(
RunnerCardService.runnerCardControllerRemove(card.id, true)
);
}
await Promise.all(prom);
Toastify({
text: $_("cards-deleted"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
//TODO: Delete cards from table
}}
>
{$_("delete-cards")}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
{/if}
<GenerateRunnerCards bind:cards_show bind:generate_cards />
</div>
<Datatable {handler}>
<table>
<thead>
<tr>
<th>
<th style="border-bottom: 1px solid #ddd;">
<input
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
@@ -95,14 +135,14 @@
<Th {handler} orderBy="code">{$_("code")}</Th>
<Th {handler} orderBy="runner">{$_("runner")}</Th>
<Th {handler} orderBy="status">{$_("status")}</Th>
<th>{$_("action")}</th>
<th style="border-bottom: 1px solid #ddd;">{$_("action")}</th>
</tr>
<tr>
<th />
<th style="border-bottom: 1px solid #ddd;" />
<ThFilter {handler} filterBy="code" />
<ThFilter {handler} filterBy="runner" />
<ThFilterRunner {handler} />
<ThFilterStatus {handler} />
<th />
<th style="border-bottom: 1px solid #ddd;" />
</tr>
</thead>
<tbody>

View File

@@ -0,0 +1,57 @@
<script>
export let handler;
let filterValue = "";
</script>
<th>
<input
on:input={() => {
setTimeout(() => {
const v = filterValue.toLowerCase();
handler.filter(v, (c) => {
// if (v === "") {
// return c;
// }
if (!c.runner && v === "blanko") {
return "blanko";
}
if (v.startsWith("#")) {
return `#${c.runner?.id}`;
}
if (c.runner) {
let runnerName = `${c.runner.firstname} ${c.runner.lastname}`;
if (c.runner.middlename) {
runnerName = `${c.runner.firstname} ${c.runner.middlename} ${c.runner.lastname}`;
}
runnerName = runnerName.toLowerCase();
return runnerName;
}
return "";
});
}, 150);
}}
bind:value={filterValue}
type="text"
name="runnerfilter"
id="runnerfilter"
/>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
input {
margin: -1px 0 0 0;
padding: 0;
width: 100%;
height: 24px;
border: none;
text-align: left;
background: inherit;
outline: 0;
font-size: 14px;
}
</style>

View File

@@ -10,10 +10,9 @@
setTimeout(() => {
if (`${selected}`.trim()) {
if (selected === "all") {
handler.filter('', 'enabled')
}
else{
handler.filter(selected, 'enabled')
handler.filter("", "enabled");
} else {
handler.filter(selected, "enabled");
}
}
}, 50);
@@ -22,8 +21,25 @@
name="statusfilter"
id="statusfilter"
>
<option value="all">{$_('all')}</option>
<option value="all">{$_("all")}</option>
<option value="true">{$_("enabled")}</option>
<option value="false">{$_("disabled")}</option>
</select>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
select {
margin: -1px 0 0 0;
padding: 0;
width: 100%;
height: 24px;
border: none;
text-align: left;
background: inherit;
outline: 0;
font-size: 14px;
}
</style>

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
GroupContactService,
RunnerTeamService,
@@ -145,7 +145,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
DonationService,
DonorService,
@@ -156,7 +156,7 @@ import { is_promise } from "svelte/internal";
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { DonationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let payment_modal_open = false;
@@ -96,7 +96,7 @@
{#if payment_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
payment_modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
DonorService
} from "@odit/lfk-client-js";
@@ -134,7 +134,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { DonorService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
@@ -32,7 +32,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
export let modal_open;
(function () {
document.onkeydown = function (e) {
@@ -25,7 +25,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -2,7 +2,7 @@
import { _, getLocaleFromNavigator } from "svelte-i18n";
import marked from "marked";
import Footer from "./Footer.svelte";
import * as css from "../base/simple.css";
// import * as css from "../base/simple.css?inline";
let html = "";
async function load() {
let md = await fetch("/privacy_" + getLocaleFromNavigator() + ".md");

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import Toastify from "toastify-js";
import { UserGroupService } from "@odit/lfk-client-js";
export let modal_open;
@@ -69,7 +69,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let modal_open;
@@ -89,7 +89,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
@@ -32,7 +32,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
RunnerService,
RunnerTeamService,
@@ -125,7 +125,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -3,7 +3,7 @@
import { read as readXlsx, utils as xlsx_utils } from "xlsx";
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import Toastify from "toastify-js";
import {
ImportService,
@@ -228,7 +228,7 @@
{#if import_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
cancelModal();

View File

@@ -1,22 +1,72 @@
<script>
import { _ } from "svelte-i18n";
let tablePageCount = [50, 100, 250, 500];
import { writable } from "svelte/store";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
} from "@tanstack/svelte-table";
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "firstname",
header: () => $_('first-name'),
filterFn: `includesString`,
},
{
accessorKey: "lastname",
header: () => $_('last-name'),
filterFn: `includesString`,
},
{
accessorKey: "group",
header: () => $_('group'),
cell: (info) => {
const group = info.getValue();
if(group.responseType === "RUNNERORGANIZATION"){return group.name}
return `${group.parentGroup.name} > ${group.name}`
},
filterFn: `includesString`,
},
{
accessorKey: "distance",
header: () => $_('distance'),
cell: (info) => {
if(info.getValue() < 1000){return `${info.getValue()} m`}
return `${(info.getValue() / 1000).toFixed(1)} km`
},
enableColumnFilter: false,
},
];
//
import {
RunnerService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import ThFilterGroup from "./ThFilterGroup.svelte";
import { DataHandler, Datatable, Th, ThFilter } from "@vincjo/datatables";
import store from "../../store";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import { onMount } from "svelte";
import InputElement from "../shared/InputElement.svelte";
$: active_deletes = [];
let dataLoaded = false;
let current_runners = [];
const handler = new DataHandler(current_runners, { rowsPerPage: 50 });
const rows = handler.getRows();
export let current_runners = [];
$: sponsoring_contracts_show = generate_runners.length > 0;
$: cards_show = generate_runners.length > 0;
$: certificates_show = generate_runners.length > 0;
@@ -31,11 +81,30 @@
return { value: g.id, label: g.name };
})
.concat(mappedteams);
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: tablePageCount[0],
},
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
onMount(() => {
RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val;
dataLoaded = true;
handler.setRows(val);
options.update((options) => ({
...options,
data: current_runners,
}));
});
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
@@ -69,134 +138,161 @@
bind:generate_runners
/>
</div>
<Datatable {handler}>
<table>
<table class="w-full">
<thead>
<tr>
<th>
<input
{#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left bg-stone-100 w-px">
<InputElement
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
checked={generate_runners.length == current_runners.length}
on:click={() => {
if (generate_runners.length != current_runners.length) {
generate_runners = current_runners;
} else {
generate_runners = [];
}
}}
checked={$table.getIsAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()}
/>
</th>
<Th {handler} orderBy="id">ID</Th>
<Th {handler} orderBy="firstname">First Name</Th>
<Th {handler} orderBy="middlename">Middle Name</Th>
<Th {handler} orderBy="lastname">Last Name</Th>
<th>Gruppe</th>
<Th {handler} orderBy="distance">Distanz</Th>
<th>{$_("action")}</th>
</tr>
<tr>
<th />
<ThFilter {handler} filterBy="id" />
<ThFilter {handler} filterBy="firstname" />
<ThFilter {handler} filterBy="middlename" />
<ThFilter {handler} filterBy="lastname" />
<ThFilterGroup groups={selectgroups} {handler} />
<th />
<th />
{#each headerGroup.headers as header}
<th class="cursor-pointer">
{#if !header.isPlaceholder}
<div on:click={header.column.getToggleSortingHandler()}>
<svelte:component
this={flexRender(
header.column.columnDef.header,
header.getContext()
)}
/>
{#if header.column.getIsSorted().toString() == "asc"}
🔼
{:else if header.column.getIsSorted().toString() == "desc"}
🔽
{/if}
</div>
{/if}
{#if header.column.getCanFilter()}
<div class="relative max-w-xs">
<input
title="name-search"
value={header.column.getFilterValue() || ""}
on:keyup={(e) => {
header.column.setFilterValue(e.target.value);
}}
type="text"
class="block rounded-md border-gray-200 py-2 pl-8 text-xs focus:border-blue-500 focus:ring-blue-500"
placeholder=""
/>
<div
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-2"
>
<svg
class="h-3.5 w-3.5 text-gray-400"
xmlns="http://www.w3.org/2000/svg"
width={16}
height={16}
fill="currentColor"
viewBox="0 0 16 16"
>
<path
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
/>
</svg>
</div>
</div>
{/if}
</th>
{/each}
</tr>
{/each}
</thead>
<tbody>
{#each $rows as row}
{#each $table.getRowModel().rows as row}
<tr>
<td>
<input
<td class="inset-y-0 left-0 px-4 py-2">
<InputElement
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
checked={generate_runners.filter((i)=>i.id == row.id).length > 0}
on:click={() => {
if (
generate_runners.findIndex((i) => i.id == row.id) == -1
) {
generate_runners.push(row);
generate_runners = generate_runners;
} else {
generate_runners = generate_runners.filter(
(r) => r.id != row.id
);
}
console.log(generate_runners)
}}
checked={row.getIsSelected()}
on:change={() => row.toggleSelected()}
/>
</td>
<td>{row.id}</td>
<td>{row.firstname}</td>
<td>{row.middlename || ""}</td>
<td>{row.lastname}</td>
<td
>{#if row.group.responseType === "RUNNERTEAM"}
<a
href="../teams/{row.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{row.group.parentGroup.name} &gt; {row.group.name}</a
>
{/if}
{#if row.group.responseType === "RUNNERORGANIZATION"}
<a
href="../orgs/{row.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{row.group.name}</a
>
{/if}</td
>
<td>{row.distance / 1000} km</td>
{#each row.getVisibleCells() as cell}
<td>
{#if active_deletes[row.id] === true}
<button
on:click={() => {
active_deletes[row.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button
on:click={() => {
RunnerService.runnerControllerRemove(row.id, true)
.then((resp) => {
current_runners = current_runners.filter(
(obj) => obj.id !== row.id
);
})
.catch((err) => {});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
{:else}
<a
href="./{row.id}"
class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:DELETE")}
<button
on:click={() => {
active_deletes[row.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
{/if}
<svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
</Datatable>
<div class="h-2" />
<div class="flex items-center gap-2">
<button
class="border rounded p-1"
on:click={() => $table.setPageIndex(0)}
disabled={!$table.getCanPreviousPage()}
>
{"<<"}
</button>
<button
class="border rounded p-1"
on:click={() => $table.previousPage()}
disabled={!$table.getCanPreviousPage()}
>
{"<"}
</button>
<button
class="border rounded p-1"
on:click={() => $table.nextPage()}
disabled={!$table.getCanNextPage()}
>
{">"}
</button>
<button
class="border rounded p-1"
on:click={() => $table.setPageIndex($table.getPageCount() - 1)}
disabled={!$table.getCanNextPage()}
>
{">>"}
</button>
<span class="flex items-center gap-1">
<div>Page</div>
<strong>
{$table.getState().pagination.pageIndex + 1} of{" "}
{$table.getPageCount()}
</strong>
</span>
<span class="flex items-center gap-1">
| Go to page:
<input
type="number"
defaultValue={$table.getState().pagination.pageIndex + 1}
on:change={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
$table.setPageIndex(page);
}}
class="border p-1 rounded w-16"
/>
</span>
<select
value={$table.getState().pagination.pageSize}
on:change={(e) => {
const ps = Number(e.target.value);
console.log({ ps });
$table.setPageSize(Number(e.target.value));
}}
>
{#each tablePageCount as pageSize}
<option value={pageSize}>{pageSize}</option>
{/each}
</select>
</div>
<pre>{JSON.stringify($table.getState(), null, 2)}</pre>
<div>
{Object.keys(selected).length} of{" "}
{$table.getPreFilteredRowModel().rows.length} Total Rows Selected
</div>
{/if}
{/if}

View File

@@ -5,7 +5,7 @@
let selected = "all";
</script>
<th>
<th style="border-bottom: 1px solid #ddd;">
<select
on:input={() => {
setTimeout(() => {

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
RunnerService,
ScanService,
@@ -81,7 +81,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,125 +1,147 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
ScanService,
} from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import { DataHandler, Datatable, Th, ThFilter } from "@vincjo/datatables";
import { ScanService, TrackService } from "@odit/lfk-client-js";
import store from "../../store";
import Toastify from "toastify-js";
import ScansEmptyState from "./ScansEmptyState.svelte";
$: searchvalue = "";
import ThFilterRunner from "./ThFilterRunner.svelte";
import ThFilterTrack from "./ThFilterTrack.svelte";
$: active_deletes = [];
export let current_scans = [];
const handler = new DataHandler(current_scans, { rowsPerPage: 20 });
const rows = handler.getRows();
const scans_promise = ScanService.scanControllerGetAll().then((val) => {
current_scans = val;
handler.setRows(val);
});
$: allTracks = [];
TrackService.trackControllerGetAll().then((val) => {
allTracks = val;
});
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
}
function format_laptime(laptime) {
if(laptime == 0 || laptime == null){return $_('first-scan-of-the-day')}
if(laptime < 60){return `${laptime}s`}
if(laptime < 3600){return `${Math.floor(laptime / 60)}min ${laptime - (Math.floor(laptime / 60)*60)}s`}
return `${Math.floor(laptime / 3600)}h ${laptime - (Math.floor(laptime / 3600)*3600)}min ${laptime - (Math.floor(laptime / 3600)*3600) - (Math.floor(laptime / 60)*60)}`
if (laptime == 0 || laptime == null) {
return $_("first-scan-of-the-day");
}
if (laptime < 60) {
return `${laptime}s`;
}
if (laptime < 3600) {
return `${Math.floor(laptime / 60)}min ${
laptime - Math.floor(laptime / 60) * 60
}s`;
}
return `${Math.floor(laptime / 3600)}h ${
laptime - Math.floor(laptime / 3600) * 3600
}min ${
laptime -
Math.floor(laptime / 3600) * 3600 -
Math.floor(laptime / 60) * 60
}`;
}
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')}
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")}
{#await scans_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('scans-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
role="alert"
>
<p class="font-bold">{$_("scans-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then}
{#if current_scans.length === 0}
<ScansEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<Datatable {handler}>
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<Th {handler} orderBy="id">ID</Th>
<Th {handler}>
{$_("runner")}
</Th>
<Th {handler}>
{$_("distance")}
</Th>
<Th {handler}>
{$_("track")}
</Th>
<Th {handler}>
{$_("laptime")}
</Th>
<Th {handler}>
{$_("status")}
</Th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('runner')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('distance-track')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('laptime')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('status')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
class="relative px-6 py-3"
style="border-bottom: 1px solid #ddd;"
>
{$_("action")}
</th>
</tr>
<tr>
<ThFilter {handler} filterBy="id" />
<ThFilterRunner {handler} />
<th style="border-bottom: 1px solid #ddd;" />
<ThFilterTrack tracks={allTracks} {handler} />
<!-- <th style="border-bottom: 1px solid #ddd;" /> -->
<th style="border-bottom: 1px solid #ddd;" />
<th style="border-bottom: 1px solid #ddd;" />
<!-- TODO: filter status -->
<th style="border-bottom: 1px solid #ddd;" />
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_scans as scan}
{#if scan.track?.name
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || scan.runner?.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || scan.runner?.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(scan.id)}
{#each $rows as scan}
<tr data-rowid="scan_{scan.id}">
<td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900">
{scan.id}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<a
href="../runners/{scan.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.runner.firstname}
{scan.runner.middlename || ''}
{scan.runner.lastname}</a>
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{scan.runner.firstname}
{scan.runner.middlename || ""}
{scan.runner.lastname}</a
>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900">
{#if scan.distance < 1000}
{scan.distance}m
{:else}{scan.distance / 1000}km{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900">
{#if scan.track}
<a
href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.track.name}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{scan.track.name}
</a>
{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<td class="px-6 py-4 whitespace-nowrap text-left">
{#if scan.responseType === "TRACKSCAN"}
<div class="text-sm font-medium text-gray-900">
{format_laptime(scan.lapTime)}
</div>
{:else}
<div class="text-sm font-medium text-gray-900">
{$_('scan-with-fixed-distance')}
{$_("scan-with-fixed-distance")}
</div>
{/if}
</td>
@@ -127,23 +149,30 @@
<div class="flex items-center">
{#if scan.valid}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('valid')}</span>
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("valid")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('invalid')}</span>
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("invalid")}</span
>
{/if}
</div>
</td>
{#if active_deletes[scan.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button
on:click={() => {
active_deletes[scan.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button
on:click={() => {
ScanService.scanControllerRemove(scan.id, false).then(
@@ -152,44 +181,51 @@
(obj) => obj.id !== scan.id
);
Toastify({
text: 'Scan deleted',
text: "Scan deleted",
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a
href="./{scan.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:DELETE')}
class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:DELETE")}
<button
on:click={() => {
active_deletes[scan.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</Datatable>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
<b class="capitalize">{$_("general_promise_error")}</b>
{error}
</span>
</div>

View File

@@ -0,0 +1,50 @@
<script>
export let handler;
let filterValue = "";
</script>
<th>
<input
on:input={() => {
setTimeout(() => {
const v = filterValue.toLowerCase();
handler.filter(v, (c) => {
if (v.startsWith("#")) {
return `#${c.runner?.id}`;
}
if (c.runner) {
let runnerName = `${c.runner.firstname} ${c.runner.lastname}`;
if (c.runner.middlename) {
runnerName = `${c.runner.firstname} ${c.runner.middlename} ${c.runner.lastname}`;
}
runnerName = runnerName.toLowerCase();
return runnerName;
}
return "";
});
}, 150);
}}
placeholder="Filter"
bind:value={filterValue}
type="text"
name="runnerfilter"
id="runnerfilter"
/>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
input {
margin: -1px 0 0 0;
padding: 0;
width: 100%;
height: 24px;
border: none;
text-align: left;
background: inherit;
outline: 0;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,31 @@
<script>
import { _ } from "svelte-i18n";
export let tracks;
export let handler;
let selected = "all";
</script>
<th style="border-bottom: 1px solid #ddd;">
<select
on:input={() => {
setTimeout(() => {
if (`${selected}`.trim()) {
const value = selected;
handler.filter(value, (scan) => {
// TODO: fix filter
if (scan.track.id === value || value === "all") return scan.track.id;
return "";
});
}
}, 50);
}}
bind:value={selected}
name="trackfilter"
id="trackfilter"
>
<option value="all">{$_("all")}</option>
{#each tracks as track}
<option value={track.id}>{track.name}</option>
{/each}
</select>
</th>

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { ScanStationService, TrackService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import Select from "svelte-select";
@@ -81,7 +81,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { ScanStationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
@@ -32,7 +32,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div

View File

@@ -1,6 +1,6 @@
<script>
import { _ } from "svelte-i18n";
import { focusTrap } from "svelte-focus-trap";
import Toastify from "toastify-js";
import { tick, createEventDispatcher } from "svelte";
export let copy_modal_open;
@@ -46,7 +46,7 @@
{#if valueCopy != null}
<textarea bind:this={areaDom}>{valueCopy}</textarea>
{/if}
<div class="fixed z-10 inset-0 overflow-y-auto" use:focusTrap>
<div class="fixed z-10 inset-0 overflow-y-auto" >
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { MeService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
@@ -30,7 +30,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div

View File

@@ -0,0 +1,20 @@
<script>
let className = "";
export { className as class };
export let type;
</script>
<input
class={`border-1 border-stone-300 border rounded-md shadow ${className} ${
type === "checkbox" && "w-5 h-5 text-orange-400"
}`}
{type}
{...$$restProps}
on:click
on:change
on:keydown
on:keyup
on:mouseenter
on:mouseleave
/>

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { StatsClientService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let modal_open;
@@ -66,7 +66,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { ScanStationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
@@ -32,7 +32,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div

View File

@@ -1,6 +1,6 @@
<script>
import { _ } from "svelte-i18n";
import { focusTrap } from "svelte-focus-trap";
import Toastify from "toastify-js";
import { tick, createEventDispatcher } from "svelte";
export let copy_modal_open;
@@ -46,7 +46,7 @@
{#if valueCopy != null}
<textarea bind:this={areaDom}>{valueCopy}</textarea>
{/if}
<div class="fixed z-10 inset-0 overflow-y-auto" use:focusTrap>
<div class="fixed z-10 inset-0 overflow-y-auto" >
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
RunnerOrganizationService,
RunnerTeamService,
@@ -77,7 +77,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerTeamService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
@@ -29,7 +29,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { tracks as tracksstore } from "../../store.js";
import { TrackService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
@@ -75,7 +75,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -1,7 +1,7 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { UserService } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail";
import Toastify from "toastify-js";
@@ -92,7 +92,7 @@
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;

View File

@@ -82,14 +82,6 @@
<tr data-rowid="user_{u.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if u.profilePic}
<div class="flex-shrink-0 h-10 w-10">
<img
class="h-10 w-10 rounded-full"
src={u.profilePic}
alt="" />
</div>
{/if}
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{u.firstname}

View File

@@ -473,5 +473,7 @@
"you-have-to-provide-an-organization": "Du musst eine Organisation angeben",
"you-have-to-save-your-changes-to-generate-a-link": "Du musst deine Änderungen speichern, um einen Link zu generieren.",
"you-must-create-at-least-one-card-or-cancel": "Du musst mindestens eine Blankokarte erstellen (oder abbrechen).",
"zip-postal-code": "Postleitzahl"
"zip-postal-code": "Postleitzahl",
"delete-cards": "Karten löschen",
"cards-deleted": "Karten gelöscht"
}

View File

@@ -473,5 +473,7 @@
"you-have-to-provide-an-organization": "You have to provide an organization",
"you-have-to-save-your-changes-to-generate-a-link": "You have to save your changes to generate a link.",
"you-must-create-at-least-one-card-or-cancel": "You must create at least one card (or cancel).",
"zip-postal-code": "ZIP/ postal code"
"zip-postal-code": "ZIP/ postal code",
"delete-cards": "Delete cards",
"cards-deleted": "Cards deleted"
}

View File

@@ -1,6 +1,6 @@
module.exports = {
mode: 'jit',
purge: [ './src/**/*.svelte' ],
content: [ './src/**/*.svelte' ],
theme: {
extend: {
colors: {

View File

@@ -1,39 +1,11 @@
import svelte from '@sveltejs/vite-plugin-svelte';
import { minify } from 'html-minifier';
// vite.config.js
import { defineConfig } from 'vite';
//
const indexReplace = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
return minify(html, {
collapseWhitespace: true
});
}
};
};
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig(({ command, mode }) => {
const isProduction = mode === 'production';
return {
// base: './',
build: {
polyfillDynamicImport: false,
cssCodeSplit: false,
minify: isProduction,
target: ["es2020", "esnext", "edge88", "chrome87", "safari14"]
},
export default defineConfig({
plugins: [
svelte({
//@ts-ignore
hot: !isProduction,
emitCss: true,
extensions: ['.md', '.svx', '.svelte'],
preprocess: [
//
/* plugin options */
})
]
}),
indexReplace()
]
};
});