🚀RELEASE v0.9.0
All checks were successful
continuous-integration/drone/tag Build is passing

This commit is contained in:
Nicolai Ort 2021-03-26 17:15:42 +01:00
parent 2932f4591e
commit e77af3a0c8
18 changed files with 3270 additions and 3225 deletions

View File

@ -2,8 +2,53 @@
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.
#### [0.9.0](https://git.odit.services/lfk/frontend/compare/0.8.7...0.9.0)
- Merge pull request 'Runner cards feature/94-runnercard_mgnt' (#111) from feature/94-runnercard_mgnt into dev [`2932f45`](https://git.odit.services/lfk/frontend/commit/2932f4591e62187a4903511051d110e9679c0993)
- Sorted translations 🌍 [`1665a1a`](https://git.odit.services/lfk/frontend/commit/1665a1a093862a13be78ec65dcdf64eb7d855593)
- Added translations [`6b5945a`](https://git.odit.services/lfk/frontend/commit/6b5945add86a77630c500872545bb91724b2047f)
- Sorted translations 🌍 [`d6c315a`](https://git.odit.services/lfk/frontend/commit/d6c315ab8e020bc65b967e2c3f4cd921392d66d5)
- Sorted translations [`de2fe0e`](https://git.odit.services/lfk/frontend/commit/de2fe0e9f171efb3deeea8cfe638f60e3ca90423)
- Added basic cards page [`5c5ef95`](https://git.odit.services/lfk/frontend/commit/5c5ef95d2be65c0e951dcd472113c8ce0593c9e0)
- Moved contract generation to it's own component [`0cfc87f`](https://git.odit.services/lfk/frontend/commit/0cfc87fbe6adfacab5c2fab732866aead3231fbf)
- Teams now use the new sponsoring contracts module [`014ba3b`](https://git.odit.services/lfk/frontend/commit/014ba3bf6718ff28f35c67c8f732b53aae50723c)
- Basic card generation worX 🎉🎉 [`d467475`](https://git.odit.services/lfk/frontend/commit/d467475b6d61d50bec3a043ea8792533e8593df6)
- Orgs now use the new sponsoring contracts module [`8b451b3`](https://git.odit.services/lfk/frontend/commit/8b451b3c6794e7df09898a687533ce8fadd56192)
- Added runnercard detail/edit modal [`0313f8c`](https://git.odit.services/lfk/frontend/commit/0313f8cc495088df1237d00e6b9ed1a94f019644)
- Implemented Add card modal [`535b23a`](https://git.odit.services/lfk/frontend/commit/535b23ae917de154e08962f5d486c50d6e091fe0)
- Added bulk card creation modal [`8a32569`](https://git.odit.services/lfk/frontend/commit/8a32569a3be1ad26ba163f4e2b67a368cfeeb422)
- Added basic card overview [`c6a1526`](https://git.odit.services/lfk/frontend/commit/c6a15264b3d13d516f3d97ea4b891ed1c328cead)
- Fixed org generation not hiding the generation toast [`c87321f`](https://git.odit.services/lfk/frontend/commit/c87321f804858f84fcccd85a15b9c3fb003c18be)
- Working runner runnercard generation [`c907486`](https://git.odit.services/lfk/frontend/commit/c907486c4d1c64114124deb3cd0d0cf11d38a6b1)
- Implemented bulk creation [`7ad6b73`](https://git.odit.services/lfk/frontend/commit/7ad6b73574174f24f2d6f23b3caf4823881a85e7)
- Now w/ working dialog🎉🎉🎉 [`ae96730`](https://git.odit.services/lfk/frontend/commit/ae9673070c3959ff6190a37123f3fc609b182c5a)
- Now w/working editing [`fac059f`](https://git.odit.services/lfk/frontend/commit/fac059f02cae84261443ee95448ec8db06dd755a)
- Added runnercard generation for teams [`23e0b53`](https://git.odit.services/lfk/frontend/commit/23e0b53107623c505d07a99a51ce836c9324acce)
- Added bulk creation modal to cards view [`f46ccb6`](https://git.odit.services/lfk/frontend/commit/f46ccb610e01654a4ee5e47d78ab500045dd494b)
- Added a new runenrcard logo [`acf78a8`](https://git.odit.services/lfk/frontend/commit/acf78a88221d0988f6501ae341e028a4113b578d)
- Moved modal import to overview for simplification [`1a52aaf`](https://git.odit.services/lfk/frontend/commit/1a52aaf8d1ad19b03d355aec0e1c48182db024f9)
- Added CardsEmptyState + Emtystate graphic [`2d0beaa`](https://git.odit.services/lfk/frontend/commit/2d0beaaaad4efefd036bbef09f10c8c22bdb2760)
- Added message for missing runner/blanco card) [`4715978`](https://git.odit.services/lfk/frontend/commit/4715978f810bbb283876f06d147b1ec86d373786)
- Fixed counting bug [`f5c1ec9`](https://git.odit.services/lfk/frontend/commit/f5c1ec9939d856804c9ec3ead4b3ed869fc2ea63)
- Added card generation/printing from detail [`4a36fb6`](https://git.odit.services/lfk/frontend/commit/4a36fb6d952d9fe4d5edbe1ed0779c7fbcd50ef0)
- Formatting [`a516aa7`](https://git.odit.services/lfk/frontend/commit/a516aa7775faa2244862bb2e3c4de623c6405e5b)
- Moved the pdf generation related componenets to their own folder [`ddd82a7`](https://git.odit.services/lfk/frontend/commit/ddd82a71a7b67ead892626addfd56ba4cc632750)
- Now routing the cards page [`e852305`](https://git.odit.services/lfk/frontend/commit/e852305400a139f8169350077c30012aed556828)
- Fuggin snowpack bs [`44bc148`](https://git.odit.services/lfk/frontend/commit/44bc14820fed26d5e0d8b12ecd6b46ca0608ae7b)
- Formatting [`9f7d223`](https://git.odit.services/lfk/frontend/commit/9f7d2234fb9603a7391ec9a64253724c2c25c333)
- Now with working org runenrcard generation [`4b3d38b`](https://git.odit.services/lfk/frontend/commit/4b3d38b05b3ed74fc3c0d77e00fa2ed245e6325c)
- Now importing runner overview [`77e9c20`](https://git.odit.services/lfk/frontend/commit/77e9c205f94cf56c2e3584444899adb1e8bdf3c6)
- CardsOverview - move to 'enabled' language key [`df53c07`](https://git.odit.services/lfk/frontend/commit/df53c0745035a220d4c07fdce1b5a5e4d763411d)
- ✒ typo - "Geb" -> "Gebe" [`f794af0`](https://git.odit.services/lfk/frontend/commit/f794af0950de59a7a7b468c30abdcb5c145f65fb)
- Bumped lfk client lib version [`3cd0468`](https://git.odit.services/lfk/frontend/commit/3cd0468b1921824b131178cb02677540b079f9b0)
- drop console log - CardDetailModal [`40899e9`](https://git.odit.services/lfk/frontend/commit/40899e9d80ba07a3fbbcac72782db53d98dc318e)
- Removed debug info [`55693de`](https://git.odit.services/lfk/frontend/commit/55693de93420c2d76af296fcacc6bcad644a3cbf)
#### [0.8.7](https://git.odit.services/lfk/frontend/compare/0.8.6...0.8.7) #### [0.8.7](https://git.odit.services/lfk/frontend/compare/0.8.6...0.8.7)
> 25 March 2021
- 🚀RELEASE v0.8.7 [`0af2647`](https://git.odit.services/lfk/frontend/commit/0af26479656393b0baea88f6f83c778740a67e62)
- Fixed listen on wrong permission🐞 [`0844215`](https://git.odit.services/lfk/frontend/commit/08442154f4bf94fc1101808b4585dc1f95afe8b2) - Fixed listen on wrong permission🐞 [`0844215`](https://git.odit.services/lfk/frontend/commit/08442154f4bf94fc1101808b4585dc1f95afe8b2)
#### [0.8.6](https://git.odit.services/lfk/frontend/compare/0.8.5...0.8.6) #### [0.8.6](https://git.odit.services/lfk/frontend/compare/0.8.5...0.8.6)

View File

@ -14,7 +14,7 @@
</head> </head>
<body> <body>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.8.7-RELEASE_INFO</span> <span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.9.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>
<script defer type="module" src="/_dist_/index.js"></script> <script defer type="module" src="/_dist_/index.js"></script>

View File

@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "0.8.7", "version": "0.9.0",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",
"dev:all": "yarn prebuild && snowpack dev", "dev:all": "yarn prebuild && snowpack dev",

View File

@ -1,224 +1,224 @@
<script> <script>
import "./TailwindStyles.svelte"; import "./TailwindStyles.svelte";
import "toastify-js/src/toastify.css"; import "toastify-js/src/toastify.css";
import "gridjs/dist/theme/mermaid.css"; import "gridjs/dist/theme/mermaid.css";
import { Route, router } from "tinro"; import { Route, router } from "tinro";
router.subscribe((routeInfo) => { router.subscribe((routeInfo) => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
}); });
if (config.prefersHashRouting) { if (config.prefersHashRouting) {
if (config.prefersHashRouting === true) { if (config.prefersHashRouting === true) {
router.useHashNavigation(); router.useHashNavigation();
} }
} }
import localForage from "localforage"; import localForage from "localforage";
import { addMessages, init, getLocaleFromNavigator } from "svelte-i18n"; import { addMessages, init, getLocaleFromNavigator } from "svelte-i18n";
import en from "./locales/en.json"; import en from "./locales/en.json";
import de from "./locales/de.json"; import de from "./locales/de.json";
addMessages("en", en); addMessages("en", en);
addMessages("de", de); addMessages("de", de);
init({ init({
fallbackLocale: "en", fallbackLocale: "en",
initialLocale: getLocaleFromNavigator(), initialLocale: getLocaleFromNavigator(),
}); });
localForage.config({ localForage.config({
name: "lfk_admin", name: "lfk_admin",
version: 1.0, version: 1.0,
storeName: "lfk_admin", storeName: "lfk_admin",
description: "LfK! admin dashbaord", description: "LfK! admin dashbaord",
}); });
window.onunhandledrejection = (event) => { window.onunhandledrejection = (event) => {
if (event.reason.toString() == "Error: Unauthorized") { if (event.reason.toString() == "Error: Unauthorized") {
console.log("Found 1"); console.log("Found 1");
localForage.clear(); localForage.clear();
location.replace("/"); location.replace("/");
} }
}; };
// //
import Login from "./components/auth/Login.svelte"; import Login from "./components/auth/Login.svelte";
import Dashboard from "./components/dashboard/Dashboard.svelte"; import Dashboard from "./components/dashboard/Dashboard.svelte";
import store from "./store.js"; import store from "./store.js";
import ForgotPassword from "./components/auth/ForgotPassword.svelte"; import ForgotPassword from "./components/auth/ForgotPassword.svelte";
import MainDashContent from "./components/dashboard/MainDashContent.svelte"; import MainDashContent from "./components/dashboard/MainDashContent.svelte";
import Users from "./components/users/Users.svelte"; import Users from "./components/users/Users.svelte";
import About from "./components/general/About.svelte"; import About from "./components/general/About.svelte";
import Settings from "./components/settings/Settings.svelte"; import Settings from "./components/settings/Settings.svelte";
import Transition from "./components/base/Transition.svelte"; import Transition from "./components/base/Transition.svelte";
import Orgs from "./components/orgs/Orgs.svelte"; import Orgs from "./components/orgs/Orgs.svelte";
import Runners from "./components/runners/Runners.svelte"; import Runners from "./components/runners/Runners.svelte";
import Footer from "./components/general/Footer.svelte"; import Footer from "./components/general/Footer.svelte";
import TracksOverview from "./components/tracks/TracksOverview.svelte"; import TracksOverview from "./components/tracks/TracksOverview.svelte";
import OrgDetail from "./components/orgs/OrgDetail.svelte"; import OrgDetail from "./components/orgs/OrgDetail.svelte";
import Teams from "./components/teams/Teams.svelte"; import Teams from "./components/teams/Teams.svelte";
import { OpenAPI } from "@odit/lfk-client-js"; import { OpenAPI } from "@odit/lfk-client-js";
import UserDetail from "./components/users/UserDetail.svelte"; import UserDetail from "./components/users/UserDetail.svelte";
OpenAPI.BASE = config.baseurl; OpenAPI.BASE = config.baseurl;
import { register as registerSW } from "./swmodule"; import { register as registerSW } from "./swmodule";
import TeamDetail from "./components/teams/TeamDetail.svelte"; import TeamDetail from "./components/teams/TeamDetail.svelte";
import UserPermissions from "./components/users/UserPermissions.svelte"; import UserPermissions from "./components/users/UserPermissions.svelte";
import GroupPermissions from "./components/groups/GroupPermissions.svelte"; import GroupPermissions from "./components/groups/GroupPermissions.svelte";
import RunnerDetail from "./components/runners/RunnerDetail.svelte"; import RunnerDetail from "./components/runners/RunnerDetail.svelte";
import Imprint from "./components/general/Imprint.svelte"; import Imprint from "./components/general/Imprint.svelte";
import Privacy from "./components/general/Privacy.svelte"; import Privacy from "./components/general/Privacy.svelte";
import ResetPassword from "./components/auth/ResetPassword.svelte"; import ResetPassword from "./components/auth/ResetPassword.svelte";
import Contacts from "./components/contacts/Contacts.svelte"; import Contacts from "./components/contacts/Contacts.svelte";
import ContactDetail from "./components/contacts/ContactDetail.svelte"; import ContactDetail from "./components/contacts/ContactDetail.svelte";
import Donors from "./components/donors/Donors.svelte"; import Donors from "./components/donors/Donors.svelte";
import Groups from "./components/groups/Groups.svelte"; import Groups from "./components/groups/Groups.svelte";
import DonorDetail from "./components/donors/DonorDetail.svelte"; import DonorDetail from "./components/donors/DonorDetail.svelte";
import Donations from "./components/donations/Donations.svelte"; import Donations from "./components/donations/Donations.svelte";
import DonationDetail from "./components/donations/DonationDetail.svelte"; import DonationDetail from "./components/donations/DonationDetail.svelte";
import GroupDetail from "./components/groups/GroupDetail.svelte"; import GroupDetail from "./components/groups/GroupDetail.svelte";
import ScanStations from "./components/scanstations/ScanStations.svelte"; import ScanStations from "./components/scanstations/ScanStations.svelte";
import ScanStationDetail from "./components/scanstations/ScanStationDetail.svelte"; import ScanStationDetail from "./components/scanstations/ScanStationDetail.svelte";
import Scans from "./components/scans/Scans.svelte"; import Scans from "./components/scans/Scans.svelte";
import ScanDetail from "./components/scans/ScanDetail.svelte"; import ScanDetail from "./components/scans/ScanDetail.svelte";
import Cards from "./components/cards/Cards.svelte"; import Cards from "./components/cards/Cards.svelte";
store.init(); store.init();
registerSW(); registerSW();
</script> </script>
<Route> <Route>
{#if $router.path === '/forgot_password'} {#if $router.path === '/forgot_password'}
<Route path="/forgot_password"> <Route path="/forgot_password">
<ForgotPassword /> <ForgotPassword />
</Route> </Route>
{:else if $router.path.includes('/reset')} {:else if $router.path.includes('/reset')}
<Route path="/reset/:resetkey" let:params> <Route path="/reset/:resetkey" let:params>
<ResetPassword {params} /> <ResetPassword {params} />
</Route> </Route>
{:else if $router.path === '/about'} {:else if $router.path === '/about'}
<Route path="/about"> <Route path="/about">
<About /> <About />
</Route> </Route>
{:else if $router.path === '/imprint'} {:else if $router.path === '/imprint'}
<Route path="/imprint"> <Route path="/imprint">
<Imprint /> <Imprint />
</Route> </Route>
{:else if $router.path === '/privacy'} {:else if $router.path === '/privacy'}
<Route path="/privacy"> <Route path="/privacy">
<Privacy /> <Privacy />
</Route> </Route>
{:else if $store.isLoggedIn} {:else if $store.isLoggedIn}
<Dashboard> <Dashboard>
<Transition> <Transition>
<Route path="/"> <Route path="/">
<MainDashContent /> <MainDashContent />
</Route> </Route>
<Route path="/users/*"> <Route path="/users/*">
<Route path="/"> <Route path="/">
<Users /> <Users />
</Route> </Route>
<Route path="/:userid/*" let:params> <Route path="/:userid/*" let:params>
<Route path="/"> <Route path="/">
<UserDetail {params} /> <UserDetail {params} />
</Route> </Route>
<Route path="/permissions/"> <Route path="/permissions/">
<UserPermissions {params} /> <UserPermissions {params} />
</Route> </Route>
</Route> </Route>
</Route> </Route>
<Route path="/groups/*"> <Route path="/groups/*">
<Route path="/"> <Route path="/">
<Groups /> <Groups />
</Route> </Route>
<Route path="/:groupid/*" let:params> <Route path="/:groupid/*" let:params>
<Route path="/"> <Route path="/">
<GroupDetail {params} /> <GroupDetail {params} />
</Route> </Route>
<Route path="/permissions/"> <Route path="/permissions/">
<GroupPermissions {params} /> <GroupPermissions {params} />
</Route> </Route>
</Route> </Route>
</Route> </Route>
<Route path="/tracks/*"> <Route path="/tracks/*">
<Route path="/"> <Route path="/">
<TracksOverview /> <TracksOverview />
</Route> </Route>
<Route path="/:trackid" let:params /> <Route path="/:trackid" let:params />
</Route> </Route>
<Route path="/runners/*"> <Route path="/runners/*">
<Route path="/"> <Route path="/">
<Runners /> <Runners />
</Route> </Route>
<Route path="/:runnerid" let:params> <Route path="/:runnerid" let:params>
<RunnerDetail {params} /> <RunnerDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/teams/*"> <Route path="/teams/*">
<Route path="/"> <Route path="/">
<Teams /> <Teams />
</Route> </Route>
<Route path="/:teamid" let:params> <Route path="/:teamid" let:params>
<TeamDetail {params} /> <TeamDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/contacts/*"> <Route path="/contacts/*">
<Route path="/"> <Route path="/">
<Contacts /> <Contacts />
</Route> </Route>
<Route path="/:contact" let:params> <Route path="/:contact" let:params>
<ContactDetail {params} /> <ContactDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/orgs/*"> <Route path="/orgs/*">
<Route path="/"> <Route path="/">
<Orgs /> <Orgs />
</Route> </Route>
<Route path="/:orgid" let:params> <Route path="/:orgid" let:params>
<OrgDetail {params} /> <OrgDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/donors/*"> <Route path="/donors/*">
<Route path="/"> <Route path="/">
<Donors /> <Donors />
</Route> </Route>
<Route path="/:donorid" let:params> <Route path="/:donorid" let:params>
<DonorDetail {params} /> <DonorDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/donations/*"> <Route path="/donations/*">
<Route path="/"> <Route path="/">
<Donations /> <Donations />
</Route> </Route>
<Route path="/:donationid" let:params> <Route path="/:donationid" let:params>
<DonationDetail {params} /> <DonationDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/cards/*"> <Route path="/cards/*">
<Route path="/"> <Route path="/">
<Cards /> <Cards />
</Route> </Route>
<!-- <Route path="/:scanid" let:params> <!-- <Route path="/:scanid" let:params>
<ScanDetail {params} /> <ScanDetail {params} />
</Route> --> </Route> -->
</Route> </Route>
<Route path="/scans/*"> <Route path="/scans/*">
<Route path="/"> <Route path="/">
<Scans /> <Scans />
</Route> </Route>
<Route path="/:scanid" let:params> <Route path="/:scanid" let:params>
<ScanDetail {params} /> <ScanDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/scanstations/*"> <Route path="/scanstations/*">
<Route path="/"> <Route path="/">
<ScanStations /> <ScanStations />
</Route> </Route>
<Route path="/:stationid" let:params> <Route path="/:stationid" let:params>
<ScanStationDetail {params} /> <ScanStationDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/about"> <Route path="/about">
<About /> <About />
</Route> </Route>
<Route path="/settings"> <Route path="/settings">
<Settings /> <Settings />
</Route> </Route>
</Transition> </Transition>
<Footer /> <Footer />
</Dashboard> </Dashboard>
{:else} {:else}
<Login /> <Login />
{/if} {/if}
</Route> </Route>

View File

@ -1,158 +1,158 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap"; import { focusTrap } from "svelte-focus-trap";
import { RunnerCardService } from "@odit/lfk-client-js"; import { RunnerCardService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let bulk_modal_open; export let bulk_modal_open;
export let current_cards; export let current_cards;
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
$: card_count = 0; $: card_count = 0;
$: is_card_count_valid = card_count > 0; $: is_card_count_valid = card_count > 0;
$: processed_last_submit = true; $: processed_last_submit = true;
$: createbtnenabled = is_card_count_valid; $: createbtnenabled = is_card_count_valid;
(() => { (() => {
document.onkeydown = (e) => { document.onkeydown = (e) => {
e = e || window.event; e = e || window.event;
if (e.key === "Escape") { if (e.key === "Escape") {
bulk_modal_open = false; bulk_modal_open = false;
} }
if (e.keyCode === 13) { if (e.keyCode === 13) {
if (createbtnenabled === true) { if (createbtnenabled === true) {
createbtnenabled = false; createbtnenabled = false;
submit(); submit();
} }
} }
}; };
})(); })();
function submit() { function submit() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: $_("creating-blanco-cards"), text: $_("creating-blanco-cards"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count) RunnerCardService.runnerCardControllerPostBlancoBulk(card_count)
.then((result) => { .then((result) => {
bulk_modal_open = false; bulk_modal_open = false;
// //
Toastify({ Toastify({
text: $_("created-blanco-cards"), text: $_("created-blanco-cards"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
}) })
.catch((err) => { .catch((err) => {
// //
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
// //
toast.hideToast(); toast.hideToast();
}); });
} }
} }
</script> </script>
{#if bulk_modal_open} {#if bulk_modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap use:focusTrap
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
bulk_modal_open = false; bulk_modal_open = false;
}}> }}>
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" /> data-id="modal_backdrop" />
</div> </div>
<span <span
class="hidden sm:inline-block sm:align-middle sm:h-screen" class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span> aria-hidden="true">&#8203;</span>
<div <div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline"> aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div <div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg <svg
class="h-6 w-6 text-blue-600" class="h-6 w-6 text-blue-600"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><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" /></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>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-bulk-blanco-cards')} {$_('create-bulk-blanco-cards')}
</h3> </h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_('just-enter-how-many-you-want-and-the-system-will-create-them')} {$_('just-enter-how-many-you-want-and-the-system-will-create-them')}
</p> </p>
</div> </div>
<div class="grid grid-cols-6 gap-6"> <div class="grid grid-cols-6 gap-6">
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="amount" for="amount"
class="block text-sm font-medium text-gray-700">{$_('amount')}</label> class="block text-sm font-medium text-gray-700">{$_('amount')}</label>
<div class="mt-1 flex rounded-md shadow-sm"> <div class="mt-1 flex rounded-md shadow-sm">
<input <input
autocomplete="off" autocomplete="off"
class:border-red-500={!is_card_count_valid} class:border-red-500={!is_card_count_valid}
class:focus:border-red-500={!is_card_count_valid} class:focus:border-red-500={!is_card_count_valid}
class:focus:ring-red-500={!is_card_count_valid} class:focus:ring-red-500={!is_card_count_valid}
bind:value={card_count} bind:value={card_count}
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-gray-500 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
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">{$_('cards')}</span> class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">{$_('cards')}</span>
</div> </div>
{#if !is_card_count_valid} {#if !is_card_count_valid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('you-must-create-at-least-one-card-or-cancel')} {$_('you-must-create-at-least-one-card-or-cancel')}
</span> </span>
{/if} {/if}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button <button
disabled={!createbtnenabled} disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled} class:opacity-50={!createbtnenabled}
on:click={submit} on:click={submit}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')} {$_('create')}
</button> </button>
<button <button
on:click={() => { on:click={() => {
bulk_modal_open = false; bulk_modal_open = false;
}} }}
type="button" type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')} {$_('cancel')}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/if} {/if}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,237 +1,237 @@
<script> <script>
import { getLocaleFromNavigator, json, _ } from "svelte-i18n"; import { getLocaleFromNavigator, json, _ } from "svelte-i18n";
import { RunnerCardService } from "@odit/lfk-client-js"; import { RunnerCardService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import CardsEmptyState from "./CardsEmptyState.svelte"; import CardsEmptyState from "./CardsEmptyState.svelte";
import CardDetailModal from "./CardDetailModal.svelte"; import CardDetailModal from "./CardDetailModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
export let edit_modal_open = false; export let edit_modal_open = false;
export let runner = {}; export let runner = {};
export let editable = {}; export let editable = {};
export let original_data = {}; export let original_data = {};
export let current_cards = []; export let current_cards = [];
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: cards_show = current_cards.some( $: cards_show = current_cards.some(
(r) => r.is_selected === true (r) => r.is_selected === true
); );
$: generate_cards = current_cards.filter((r) => r.is_selected === true); $: generate_cards = current_cards.filter((r) => r.is_selected === true);
const cards_promise = RunnerCardService.runnerCardControllerGetAll().then( const cards_promise = RunnerCardService.runnerCardControllerGetAll().then(
(val) => { (val) => {
current_cards = val; current_cards = val;
} }
); );
function should_display_based_on_id(id) { function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") { if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", "")); return id.toString().startsWith(searchvalue.replace("*", ""));
} }
return id.toString() === searchvalue; return id.toString() === searchvalue;
} }
const getRunnerLabel = (option) => const getRunnerLabel = (option) =>
option?.firstname + " " + (option?.middlename || "") + " " + (option?.lastname || "{$_('non-blanko')}"); option?.firstname + " " + (option?.middlename || "") + " " + (option?.lastname || "{$_('non-blanko')}");
function open_edit_modal(card) { function open_edit_modal(card) {
if(card.runner?.id){ if(card.runner?.id){
runner = Object.assign( runner = Object.assign(
{ runner }, { runner },
{ label: getRunnerLabel(card.runner), value: card.runner } { label: getRunnerLabel(card.runner), value: card.runner }
); );
card.runner = card.runner.id; card.runner = card.runner.id;
} }
else{ else{
card.runner=null; card.runner=null;
runner = null runner = null
} }
editable = Object.assign(editable, card); editable = Object.assign(editable, card);
original_data = Object.assign(original_data, card); original_data = Object.assign(original_data, card);
edit_modal_open = true; edit_modal_open = true;
} }
</script> </script>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:UPDATE')} {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:UPDATE')}
<CardDetailModal <CardDetailModal
bind:current_cards bind:current_cards
bind:edit_modal_open bind:edit_modal_open
bind:runner bind:runner
bind:editable bind:editable
bind:original_data /> bind:original_data />
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
{#await cards_promise} {#await cards_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">{$_('loading-cards')}</p> <p class="font-bold">{$_('loading-cards')}</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_cards.length === 0} {#if current_cards.length === 0}
<CardsEmptyState /> <CardsEmptyState />
{: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="gridjs-input gridjs-search-input mb-4" /> class="gridjs-input gridjs-search-input mb-4" />
<div class="h-12"> <div class="h-12">
<GenerateRunnerCards <GenerateRunnerCards
bind:cards_show bind:cards_show
bind:generate_cards /> bind:generate_cards />
</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> <tr>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span <span
on:click={() => { on:click={() => {
const newstate = !current_cards.some((r) => r.is_selected === true); const newstate = !current_cards.some((r) => r.is_selected === true);
current_cards = current_cards.map((r) => { current_cards = current_cards.map((r) => {
r.is_selected = newstate; r.is_selected = newstate;
return r; return r;
}); });
}} }}
class="underline cursor-pointer select-none">{#if current_cards.some((r) => r.is_selected === true)} class="underline cursor-pointer select-none">{#if current_cards.some((r) => r.is_selected === true)}
{$_('deselect-all')} {$_('deselect-all')}
{:else}{$_('select-all')}{/if} {:else}{$_('select-all')}{/if}
</span> </span>
</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">
{$_('code')} {$_('code')}
</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">
{$_('runner')} {$_('runner')}
</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">
{$_('status')} {$_('status')}
</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_cards as card} {#each current_cards as card}
{#if card.code {#if card.code
.toLowerCase() .toLowerCase()
.includes( .includes(
searchvalue.toLowerCase() searchvalue.toLowerCase()
) || card.runner?.firstname ) || card.runner?.firstname
.toLowerCase() .toLowerCase()
.includes( .includes(
searchvalue.toLowerCase() searchvalue.toLowerCase()
) || card.runner?.middlename ) || card.runner?.middlename
.toLowerCase() .toLowerCase()
.includes( .includes(
searchvalue.toLowerCase() searchvalue.toLowerCase()
) || card.runner?.lastname ) || card.runner?.lastname
.toLowerCase() .toLowerCase()
.includes( .includes(
searchvalue.toLowerCase() searchvalue.toLowerCase()
) || should_display_based_on_id(card.id)} ) || should_display_based_on_id(card.id)}
<tr data-rowid="card_{card.id}"> <tr data-rowid="card_{card.id}">
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<input <input
bind:checked={card.is_selected} bind:checked={card.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">{card.code}</div> <div class="flex items-center">{card.code}</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">
{#if card.runner} {#if card.runner}
<a <a
href="../runners/{card.runner.id}" href="../runners/{card.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{card.runner.firstname} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{card.runner.firstname}
{card.runner.middlename || ''} {card.runner.middlename || ''}
{card.runner.lastname}</a> {card.runner.lastname}</a>
{:else}{$_('non-blanko')}{/if} {:else}{$_('non-blanko')}{/if}
</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">
{#if card.enabled} {#if card.enabled}
<span <span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('enabled')}</span> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('enabled')}</span>
{:else} {:else}
<span <span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('disabled')}</span> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('disabled')}</span>
{/if} {/if}
</div> </div>
</td> </td>
{#if active_deletes[card.id] === true} {#if active_deletes[card.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[card.id] = false; active_deletes[card.id] = false;
}} }}
tabindex="0" 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 <button
on:click={() => { on:click={() => {
RunnerCardService.runnerCardControllerRemove(card.id, false).then( RunnerCardService.runnerCardControllerRemove(card.id, false).then(
(resp) => { (resp) => {
current_cards = current_cards.filter( current_cards = current_cards.filter(
(obj) => obj.id !== card.id (obj) => obj.id !== card.id
); );
Toastify({ Toastify({
text: $_('card-deleted'), text: $_('card-deleted'),
duration: 500, duration: 500,
backgroundColor: backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)', 'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast(); }).showToast();
} }
); );
}} }}
tabindex="0" 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> </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">
<button <button
on:click={() => { on:click={() => {
open_edit_modal(card); open_edit_modal(card);
}} }}
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</button> class="text-indigo-600 hover:text-indigo-900">{$_('details')}</button>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:DELETE')}
<button <button
on:click={() => { on:click={() => {
active_deletes[card.id] = true; active_deletes[card.id] = true;
}} }}
tabindex="0" 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} {/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,327 +1,327 @@
<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";
$: 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">
<nav <nav
class:-translate-x-full={!navOpen} class:-translate-x-full={!navOpen}
class:translate-x-0={navOpen} class:translate-x-0={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 md:translate-x-0 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 md:translate-x-0 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">Lauf für Kaya! Admin</h3> <h3 class="text-lg">Lauf für Kaya! 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:bg-gray-100={$router.path === '/'} class:bg-gray-100={$router.path === '/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/"> href="/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 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('ORGANIZATION:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
<a <a
class:bg-gray-100={$router.path.includes('/orgs/')} class:bg-gray-100={$router.path.includes('/orgs/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/orgs/"> href="/orgs/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
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"><path fill="none" d="M0 0h24v24H0z" /> height="24"><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" /></svg> d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg>
<span>{$_('orgs')}</span> <span>{$_('orgs')}</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:bg-gray-100={$router.path === '/users/'} class:bg-gray-100={$router.path === '/users/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 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 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M12 14v8H4a8 8 0 018-8zm0-1a6 6 0 110-12 6 6 0 010 12zm2.6 5.81a3.51 3.51 0 010-1.62l-1-.57 1-1.74 1 .58a3.5 3.5 0 011.4-.82V13.5h2v1.15a3.5 3.5 0 011.4.8l1-.57 1 1.74-1 .57a3.51 3.51 0 010 1.62l1 .57-1 1.74-1-.58a3.5 3.5 0 01-1.4.82v1.14h-2v-1.15a3.5 3.5 0 01-1.4-.8l-1 .57-1-1.74 1-.57zM18 17a1 1 0 100 2 1 1 0 000-2z" /></svg> d="M12 14v8H4a8 8 0 018-8zm0-1a6 6 0 110-12 6 6 0 010 12zm2.6 5.81a3.51 3.51 0 010-1.62l-1-.57 1-1.74 1 .58a3.5 3.5 0 011.4-.82V13.5h2v1.15a3.5 3.5 0 011.4.8l1-.57 1 1.74-1 .57a3.51 3.51 0 010 1.62l1 .57-1 1.74-1-.58a3.5 3.5 0 01-1.4.82v1.14h-2v-1.15a3.5 3.5 0 01-1.4-.8l-1 .57-1-1.74 1-.57zM18 17a1 1 0 100 2 1 1 0 000-2z" /></svg>
<span>{$_('users')}</span> <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:bg-gray-100={$router.path === '/groups/'} class:bg-gray-100={$router.path === '/groups/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/groups/"> href="/groups/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
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"><path viewBox="0 0 640 512"><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" /></svg> d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('user-groups')}</span> <span>{$_('user-groups')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
<a <a
class:bg-gray-100={$router.path === '/runners/'} class:bg-gray-100={$router.path === '/runners/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 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 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><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" /></svg> d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
<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:bg-gray-100={$router.path === '/teams/'} class:bg-gray-100={$router.path === '/teams/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/teams/"> href="/teams/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
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"><path viewBox="0 0 640 512"><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" /></svg> d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('teams')}</span> <span>{$_('teams')}</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:bg-gray-100={$router.path.includes('/donors/')} class:bg-gray-100={$router.path.includes('/donors/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donors/"> href="/donors/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
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"><path fill="none" d="M0 0h24v24H0z" /> height="24"><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" /></svg> d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg>
<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:bg-gray-100={$router.path.includes('/donations/')} class:bg-gray-100={$router.path.includes('/donations/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donations/"> href="/donations/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
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"><path fill="none" d="M0 0h24v24H0z" /> height="24"><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" /></svg> d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg>
<span>{$_('donations')}</span> <span>{$_('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:bg-gray-100={$router.path === '/tracks/'} class:bg-gray-100={$router.path === '/tracks/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/tracks/"> href="/tracks/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
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"><path viewBox="0 0 640 512"><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" /></svg> d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" /></svg>
<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:bg-gray-100={$router.path === '/cards/'} class:bg-gray-100={$router.path === '/cards/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/cards/"> href="/cards/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
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" /></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>
<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:bg-gray-100={$router.path === '/scans/'} class:bg-gray-100={$router.path === '/scans/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/scans/"> href="/scans/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
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"><path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 24 24"><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" /></svg> d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></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:bg-gray-100={$router.path === '/contacts/'} class:bg-gray-100={$router.path === '/contacts/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/contacts/"> href="/contacts/">
<svg <svg
fill="currentColor" fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><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" /></svg> d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg>
<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:bg-gray-100={$router.path === '/scanstations/'} class:bg-gray-100={$router.path === '/scanstations/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/scanstations/"> href="/scanstations/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
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"><path xmlns="http://www.w3.org/2000/svg"><path
fill="none" fill="none"
d="M0 0h24v24H0z" /> d="M0 0h24v24H0z" />
<path <path
fill="currentColor" fill="currentColor"
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
<span>{$_('scanstations')}</span> <span>{$_('scanstations')}</span>
</a> </a>
{/if} {/if}
<a <a
class:bg-gray-100={$router.path === '/settings/'} class:bg-gray-100={$router.path === '/settings/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/settings/"> href="/settings/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 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:bg-gray-100={$router.path === '/about/'} class:bg-gray-100={$router.path === '/about/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/about/"> href="/about/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
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"><circle cx="12" cy="12" r="10" /> viewBox="0 0 24 24"><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>
<span <span
tabindex="0" tabindex="0"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
on:click={() => { on:click={() => {
AuthService.authControllerLogout(); AuthService.authControllerLogout();
logout(); logout();
}}> }}>
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
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"><path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 24 24"><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" /></svg> d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z" /></svg>
<span>{$_('logout')}</span> <span>{$_('logout')}</span>
</span> </span>
</nav> </nav>
</nav> </nav>
<div class="ml-0 transition md:ml-60"> <div class="ml-0 transition md:ml-60">
<header <header
on:click={() => { on:click={() => {
navOpen = true; navOpen = true;
}} }}
class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden"> class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden">
<button class="block btn btn-light md:hidden"> <button class="block btn btn-light md:hidden">
<span class="sr-only">Menu</span><svg <span class="sr-only">Menu</span><svg
class="w-4 h-4" class="w-4 h-4"
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"><path fill="currentcolor"><path
fill-rule="evenodd" fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clip-rule="evenodd" /></svg></button> clip-rule="evenodd" /></svg></button>
</header> </header>
<slot> <slot>
<NoComponentLoaded /> <NoComponentLoaded />
</slot> </slot>
</div> </div>
<div <div
on:click={() => { on:click={() => {
navOpen = false; navOpen = false;
}} }}
class:hidden={!navOpen} class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" /> class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" />
</section> </section>

View File

@ -1,212 +1,212 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
let modal_open = false; let modal_open = false;
let delete_org = {}; let delete_org = {};
import { RunnerOrganizationService } from "@odit/lfk-client-js"; import { RunnerOrganizationService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import OrgsEmptyState from "./OrgsEmptyState.svelte"; import OrgsEmptyState from "./OrgsEmptyState.svelte";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte"; import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: sponsoring_contracts_show = current_organizations.some((r) => r.is_selected === true); $: sponsoring_contracts_show = current_organizations.some((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((r) => r.is_selected === true); $: generate_orgs = current_organizations.filter((r) => r.is_selected === true);
export let current_organizations = []; export let current_organizations = [];
const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then( const promise = 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="gridjs-input gridjs-search-input mb-4" /> class="gridjs-input gridjs-search-input mb-4" />
<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 <GenerateRunnerCards
bind:cards_show bind:cards_show
bind:generate_orgs /> 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> <tr>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span <span
on:click={() => { on:click={() => {
const newstate = !current_organizations.some((r) => r.is_selected === true); const newstate = !current_organizations.some((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">{#if current_organizations.some((r) => r.is_selected === true)} class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)}
{$_('deselect-all')} {$_('deselect-all')}
{:else}{$_('select-all')}{/if} {:else}{$_('select-all')}{/if}
</span> </span>
</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 data-rowid="org_{o.id}"> <tr 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="ml-4"> <div class="ml-4">
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{o.name} {o.name}
</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 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 o.address.address1 !== null} {#if o.address.address1 !== null}
{o.address.address1}<br /> {o.address.address1}<br />
{o.address.address2 || ''}<br /> {o.address.address2 || ''}<br />
{o.address.postalcode} {o.address.postalcode}
{o.address.city} {o.address.city}
{o.address.country} {o.address.country}
{/if} {/if}
</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 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 o.contact} {#if o.contact}
<a <a
href="../contacts/{o.contact.id}" href="../contacts/{o.contact.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname}
{o.contact.middlename || ''} {o.contact.middlename || ''}
{o.contact.lastname}</a> {o.contact.lastname}</a>
{:else}{$_('no-contact-specified')}{/if} {:else}{$_('no-contact-specified')}{/if}
</div> </div>
</div> </div>
</div> </div>
</td> </td>
{#if active_deletes[o.id] === true} {#if active_deletes[o.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[o.id] = false; active_deletes[o.id] = false;
}} }}
tabindex="0" 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 <button
on:click={() => { on:click={() => {
RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false) RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false)
.then((resp) => { .then((resp) => {
current_organizations = current_organizations.filter((obj) => obj.id !== o.id); current_organizations = current_organizations.filter((obj) => obj.id !== o.id);
Toastify({ Toastify({
text: 'Organization deleted', text: 'Organization deleted',
duration: 500, duration: 500,
backgroundColor: backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)', 'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast(); }).showToast();
}) })
.catch((err) => { .catch((err) => {
modal_open = true; modal_open = true;
delete_org = o; delete_org = o;
}); });
}} }}
tabindex="0" 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> </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="./{o.id}" href="./{o.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')}
<button <button
on:click={() => { on:click={() => {
active_deletes[o.id] = true; active_deletes[o.id] = true;
}} }}
tabindex="0" 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} {/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,339 +1,339 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { import {
RunnerCardService, RunnerCardService,
RunnerOrganizationService, RunnerOrganizationService,
RunnerTeamService, RunnerTeamService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
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; $: cards_dropdown_open = false;
document.addEventListener("click", function (e) { document.addEventListener("click", function (e) {
if ( if (
e.target.parentNode?.parentNode?.id != "cards:dropdown" && e.target.parentNode?.parentNode?.id != "cards:dropdown" &&
e.target.parentNode?.parentNode?.id != "cards:dropdown:menu" e.target.parentNode?.parentNode?.id != "cards:dropdown:menu"
) { ) {
cards_dropdown_open = false; cards_dropdown_open = false;
} }
}); });
function generateRunnerCards(locale) { function generateRunnerCards(locale) {
cards_dropdown_open = false; cards_dropdown_open = false;
if (generate_orgs.length > 0) { if (generate_orgs.length > 0) {
generateOrgCards(locale); generateOrgCards(locale);
} else if (generate_teams.length > 0) { } else if (generate_teams.length > 0) {
generateTeamCards(locale); generateTeamCards(locale);
} else if (generate_runners.length > 0) { } else if (generate_runners.length > 0) {
generateRunnersCards(locale); generateRunnersCards(locale);
} else { } else {
generateCards(locale); generateCards(locale);
} }
} }
function generateCards(locale) { function generateCards(locale) {
const toast = Toastify({ const toast = Toastify({
text: $_("generating-pdf"), text: $_("generating-pdf"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
fetch( fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`, `${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(generate_cards), body: JSON.stringify(generate_cards),
} }
) )
.then((response) => { .then((response) => {
if (response.status != "200") { if (response.status != "200") {
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdf-generation-failed"), text: $_("pdf-generation-failed"),
duration: 3500, duration: 3500,
backgroundColor: backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast(); }).showToast();
} else { } else {
return response.blob(); return response.blob();
} }
}) })
.then((blob) => { .then((blob) => {
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 = "Runnercards.pdf"; a.download = "Runnercards.pdf";
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
a.remove(); a.remove();
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdf-successfully-generated"), text: $_("pdf-successfully-generated"),
duration: 3500, duration: 3500,
backgroundColor: backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)", "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
}) })
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);
}); });
} }
async function generateRunnersCards(locale) { async function generateRunnersCards(locale) {
const toast = Toastify({ const toast = Toastify({
text: $_("generating-pdf"), text: $_("generating-pdf"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
const current_cards = await RunnerCardService.runnerCardControllerGetAll(); const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let cards = []; let cards = [];
for (let runner of generate_runners) { for (let runner of generate_runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id); let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) { if (!card) {
card = await RunnerCardService.runnerCardControllerPost({ card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id, runner: runner.id,
}); });
} }
cards.push(card); cards.push(card);
} }
fetch( fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`, `${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(cards), body: JSON.stringify(cards),
} }
) )
.then((response) => { .then((response) => {
if (response.status != "200") { if (response.status != "200") {
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdf-generation-failed"), text: $_("pdf-generation-failed"),
duration: 3500, duration: 3500,
backgroundColor: backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast(); }).showToast();
} else { } else {
return response.blob(); return response.blob();
} }
}) })
.then((blob) => { .then((blob) => {
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 = "Runnercards.pdf"; a.download = "Runnercards.pdf";
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
a.remove(); a.remove();
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdf-successfully-generated"), text: $_("pdf-successfully-generated"),
duration: 3500, duration: 3500,
backgroundColor: backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)", "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
}) })
.catch((err) => {}); .catch((err) => {});
} }
async function generateTeamCards(locale) { async function generateTeamCards(locale) {
const toast = Toastify({ const toast = Toastify({
text: $_("generating-pdfs"), text: $_("generating-pdfs"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
let count = 0; let count = 0;
const current_cards = await RunnerCardService.runnerCardControllerGetAll(); const current_cards = await RunnerCardService.runnerCardControllerGetAll();
for await (const t of generate_teams) { for await (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners( const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id t.id
); );
let cards = []; let cards = [];
for (let runner of runners) { for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id); let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) { if (!card) {
card = await RunnerCardService.runnerCardControllerPost({ card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id, runner: runner.id,
}); });
} }
cards.push(card); cards.push(card);
} }
fetch( fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`, `${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(cards), body: JSON.stringify(cards),
} }
) )
.then((response) => { .then((response) => {
if (response.status != "200") { if (response.status != "200") {
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdf-generation-failed"), text: $_("pdf-generation-failed"),
duration: 3500, duration: 3500,
backgroundColor: backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast(); }).showToast();
} else { } else {
return response.blob(); return response.blob();
} }
}) })
.then((blob) => { .then((blob) => {
count++; count++;
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 = "Sponsorings_" + t.name + ".pdf"; a.download = "Sponsorings_" + t.name + ".pdf";
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
a.remove(); a.remove();
if (count === generate_teams.length) { if (count === generate_teams.length) {
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdfs-successfully-generated"), text: $_("pdfs-successfully-generated"),
duration: 3500, duration: 3500,
backgroundColor: backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)", "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
} }
}) })
.catch((err) => {}); .catch((err) => {});
} }
} }
async function generateOrgCards(locale) { async function generateOrgCards(locale) {
const toast = Toastify({ const toast = Toastify({
text: $_("generating-pdf"), text: $_("generating-pdf"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
let count = 0; let count = 0;
const current_cards = await RunnerCardService.runnerCardControllerGetAll(); const current_cards = await RunnerCardService.runnerCardControllerGetAll();
for await (const o of generate_orgs) { for await (const o of generate_orgs) {
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners( const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id o.id
); );
let cards = []; let cards = [];
for (let runner of runners) { for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id); let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) { if (!card) {
card = await RunnerCardService.runnerCardControllerPost({ card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id, runner: runner.id,
}); });
} }
cards.push(card); cards.push(card);
} }
fetch( fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`, `${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(cards), body: JSON.stringify(cards),
} }
) )
.then((response) => { .then((response) => {
if (response.status != "200") { if (response.status != "200") {
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdf-generation-failed"), text: $_("pdf-generation-failed"),
duration: 3500, duration: 3500,
backgroundColor: backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast(); }).showToast();
} else { } else {
return response.blob(); return response.blob();
} }
}) })
.then((blob) => { .then((blob) => {
count++; count++;
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 = "Sponsorings_" + o.name + ".pdf"; a.download = "Sponsorings_" + o.name + ".pdf";
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
a.remove(); a.remove();
if (count === generate_orgs.length) { if (count === generate_orgs.length) {
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdfs-successfully-generated"), text: $_("pdfs-successfully-generated"),
duration: 3500, duration: 3500,
backgroundColor: backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)", "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
} }
}) })
.catch((err) => {}); .catch((err) => {});
} }
} }
</script> </script>
{#if cards_show} {#if cards_show}
<div id="cards:dropdown" class="relative inline-block"> <div id="cards:dropdown" class="relative inline-block">
<div> <div>
<button <button
on:click={() => { on:click={() => {
cards_dropdown_open = !cards_dropdown_open; cards_dropdown_open = !cards_dropdown_open;
}} }}
type="button" 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" 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" id="options-menu"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="true"> aria-expanded="true">
{$_('generate-runnercards')} {$_('generate-runnercards')}
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path class="-mr-1 ml-2 h-5 w-5"><path
fill="none" fill="none"
d="M0 0h24v24H0z" /> d="M0 0h24v24H0z" />
<path <path
fill="currentColor" 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> 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> </button>
</div> </div>
{#if cards_dropdown_open} {#if cards_dropdown_open}
<div <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" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="cards:dropdown:menu"> id="cards:dropdown:menu">
<div <div
class="py-1" class="py-1"
role="menu" role="menu"
aria-orientation="vertical" aria-orientation="vertical"
aria-labelledby="options-menu"> aria-labelledby="options-menu">
<span <span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button <button
on:click={() => { on:click={() => {
generateRunnerCards('de'); generateRunnerCards('de');
}} }}
type="submit" 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" 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"> role="menuitem">
{$_('german')} {$_('german')}
</button> </button>
<button <button
on:click={() => { on:click={() => {
generateRunnerCards('en'); generateRunnerCards('en');
}} }}
type="submit" 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" 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"> role="menuitem">
{$_('english')} {$_('english')}
</button> </button>
</div> </div>
</div> </div>
{/if} {/if}
</div> </div>
{/if} {/if}

View File

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

View File

@ -1,297 +1,297 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } 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 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 Toastify from "toastify-js"; import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
import Select from "svelte-select"; import Select from "svelte-select";
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;
$: 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) {
Toastify({ Toastify({
text: $_("updating-runner"), text: $_("updating-runner"),
duration: 2500, duration: 2500,
}).showToast(); }).showToast();
let postdata = {}; let postdata = {};
postdata = Object.assign(postdata, editable); postdata = Object.assign(postdata, editable);
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;
Toastify({ Toastify({
text: $_("runner-updated"), text: $_("runner-updated"),
duration: 2500, duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
}) })
.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">
<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" class="flex-shrink-0 w-5 h-5 mr-2"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><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" /></svg> d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="./">{$_('runners')}</a><svg <a class="mr-2" href="./">{$_('runners')}</a><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"><line
x1="5" x1="5"
y1="12" y1="12"
x2="19" x2="19"
y2="12" /> y2="12" />
<polyline points="12 5 19 12 12 19" /></svg> <polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="mr-2">{original_data.firstname} <span class="mr-2">{original_data.firstname}
{original_data.middlename || ''} {original_data.middlename || ''}
{original_data.lastname}</span> {original_data.lastname}</span>
</li> </li>
</ol> </ol>
</nav> </nav>
</div> </div>
</div> </div>
<div class="mb-8 text-3xl font-extrabold leading-tight"> <div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.firstname} {original_data.firstname}
{original_data.middlename || ''} {original_data.middlename || ''}
{original_data.lastname} {original_data.lastname}
<span data-id="runner_actions_${editable.id}"> <span data-id="runner_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
{#if delete_triggered} {#if delete_triggered}
<button <button
on:click={deleteRunner} 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:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button>
<button <button
on:click={() => { on:click={() => {
delete_triggered = !delete_triggered; delete_triggered = !delete_triggered;
}} }}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
{/if} {/if}
<GenerateSponsoringContracts <GenerateSponsoringContracts
bind:sponsoring_contracts_show bind:sponsoring_contracts_show
bind:generate_runners /> bind:generate_runners />
<GenerateRunnerCards <GenerateRunnerCards
bind:sponsoring_contracts_show bind:sponsoring_contracts_show
bind:generate_runners /> bind:generate_runners />
{#if !delete_triggered} {#if !delete_triggered}
<button <button
on:click={() => { on:click={() => {
delete_triggered = true; delete_triggered = true;
}} }}
type="button" type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-runner')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-runner')}</button>
{/if} {/if}
{/if} {/if}
{#if !delete_triggered} {#if !delete_triggered}
<button <button
disabled={!save_enabled} disabled={!save_enabled}
class:opacity-50={!save_enabled} class:opacity-50={!save_enabled}
type="button" type="button"
on:click={submit} on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{/if} {/if}
</span> </span>
</div> </div>
<!-- --> <!-- -->
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label
for="firstname" for="firstname"
class="font-medium text-gray-700">{$_('first-name')}</label> class="font-medium text-gray-700">{$_('first-name')}</label>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('first-name')} placeholder={$_('first-name')}
type="text" type="text"
class:border-red-500={!isFirstnameValid} class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid} class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid} class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname} bind:value={editable.firstname}
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isFirstnameValid} {#if !isFirstnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')} {$_('first-name-is-required')}
</span> </span>
{/if} {/if}
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label
for="middlename" for="middlename"
class="font-medium text-gray-700">{$_('middle-name')}</label> class="font-medium text-gray-700">{$_('middle-name')}</label>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('middle-name')} placeholder={$_('middle-name')}
type="text" type="text"
bind:value={editable.middlename} bind:value={editable.middlename}
name="middlename" name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label
for="lastname" for="lastname"
class="font-medium text-gray-700">{$_('last-name')}</label> class="font-medium text-gray-700">{$_('last-name')}</label>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('last-name')} placeholder={$_('last-name')}
type="text" type="text"
bind:value={editable.lastname} bind:value={editable.lastname}
class:border-red-500={!isLastnameValid} class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid} class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid} class:focus:ring-red-500={!isLastnameValid}
name="lastname" name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isLastnameValid} {#if !isLastnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')} {$_('last-name-is-required')}
</span> </span>
{/if} {/if}
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label
for="email" for="email"
class="font-medium text-gray-700">{$_('e-mail-adress')}</label> class="font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('e-mail-adress')} placeholder={$_('e-mail-adress')}
type="email" type="email"
bind:value={editable.email} bind:value={editable.email}
class:border-red-500={!isEmailValid} class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid} class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid} class:focus:ring-red-500={!isEmailValid}
name="email" name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValid} {#if !isEmailValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-email-is-required')} {$_('valid-email-is-required')}
</span> </span>
{/if} {/if}
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label for="phone" class="font-medium text-gray-700">{$_('phone')}</label> <label for="phone" class="font-medium text-gray-700">{$_('phone')}</label>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('phone')} placeholder={$_('phone')}
type="tel" type="tel"
bind:value={editable.phone} bind:value={editable.phone}
name="phone" name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('group')}</span> <span class="font-medium text-gray-700">{$_('group')}</span>
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => label itemFilter={(label, filterText, option) => label
.toLowerCase() .toLowerCase()
.includes( .includes(
filterText.toLowerCase() filterText.toLowerCase()
) || option.id.value ) || option.id.value
.toString() .toString()
.startsWith(filterText.toLowerCase())} .startsWith(filterText.toLowerCase())}
items={groups} items={groups}
showChevron={true} showChevron={true}
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')} placeholder={$_('search-for-an-organization-or-team-by-name-or-id')}
noOptionsMessage={$_('no-organization-or-team-found')} noOptionsMessage={$_('no-organization-or-team-found')}
bind:selectedValue={group} bind:selectedValue={group}
on:select={(selectedValue) => { on:select={(selectedValue) => {
editable.group = selectedValue.detail.value.id; editable.group = selectedValue.detail.value.id;
}} }}
on:clear={() => (editable.group = null)} /> on:clear={() => (editable.group = null)} />
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('distance')}</span> <span class="font-medium text-gray-700">{$_('distance')}</span>
<br /> <br />
<span class="text-gray-700">{original_data.distance} km</span> <span class="text-gray-700">{original_data.distance} km</span>
</div> </div>
</section> </section>
{:catch error} {:catch error}
<PromiseError {error} /> <PromiseError {error} />
{/await} {/await}

View File

@ -1,256 +1,256 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { import {
RunnerService, RunnerService,
RunnerTeamService, RunnerTeamService,
RunnerOrganizationService, RunnerOrganizationService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import RunnersEmptyState from "./RunnersEmptyState.svelte"; import RunnersEmptyState from "./RunnersEmptyState.svelte";
import Select from "svelte-select"; import Select from "svelte-select";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
export let current_runners = []; export let current_runners = [];
const runners_promise = RunnerService.runnerControllerGetAll().then((val) => { const runners_promise = RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val; current_runners = val;
}); });
$: selectedFilter_teams = null; $: selectedFilter_teams = null;
$: selectedFilter = null; $: selectedFilter = null;
$: filter__teams = selectedFilter_teams || []; $: filter__teams = selectedFilter_teams || [];
$: filter__orgs = selectedFilter || []; $: filter__orgs = selectedFilter || [];
$: filterGroupIDs = filter__teams.concat(filter__orgs).map((i) => i.value); $: filterGroupIDs = filter__teams.concat(filter__orgs).map((i) => i.value);
$: sponsoring_contracts_show = current_runners.some( $: sponsoring_contracts_show = current_runners.some(
(r) => r.is_selected === true (r) => r.is_selected === true
); );
$: cards_show = current_runners.some( $: cards_show = current_runners.some(
(r) => r.is_selected === true (r) => r.is_selected === true
); );
$: generate_runners = current_runners.filter((r) => r.is_selected === true); $: generate_runners = current_runners.filter((r) => r.is_selected === true);
$: teams = []; $: teams = [];
$: orgs = []; $: orgs = [];
$: mappedteams = teams.map(function (g) { $: mappedteams = teams.map(function (g) {
return { value: g.id, label: g.parentGroup.name + " > " + g.name }; return { value: g.id, label: g.parentGroup.name + " > " + g.name };
}); });
$: selectgroups = orgs $: selectgroups = orgs
.map(function (g) { .map(function (g) {
return { value: g.id, label: g.name }; return { value: g.id, label: g.name };
}) })
.concat(mappedteams); .concat(mappedteams);
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val; teams = val;
}); });
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val; orgs = val;
}); });
function should_display_based_on_id(id) { function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") { if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", "")); return id.toString().startsWith(searchvalue.replace("*", ""));
} }
return id.toString() === searchvalue; return id.toString() === searchvalue;
} }
</script> </script>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
{#await runners_promise} {#await runners_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">{$_('runners-are-being-loaded')}</p> <p class="font-bold">{$_('runners-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_runners.length === 0} {#if current_runners.length === 0}
<RunnersEmptyState /> <RunnersEmptyState />
{: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="gridjs-input gridjs-search-input mb-4" /> class="gridjs-input gridjs-search-input mb-4" />
<div class="block mb-6"> <div class="block mb-6">
<label <label
for="country" for="country"
class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label> class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label>
<Select <Select
on:select={(event) => { on:select={(event) => {
selectedFilter = event.detail; selectedFilter = event.detail;
}} }}
selectedValue={selectedFilter} selectedValue={selectedFilter}
placeholder={$_('filter-by-organization-team')} placeholder={$_('filter-by-organization-team')}
containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
items={selectgroups} items={selectgroups}
isMulti={true} /> isMulti={true} />
</div> </div>
<div class="h-12"> <div class="h-12">
<GenerateSponsoringContracts <GenerateSponsoringContracts
bind:sponsoring_contracts_show bind:sponsoring_contracts_show
bind:generate_runners /> bind:generate_runners />
<GenerateRunnerCards <GenerateRunnerCards
bind:cards_show bind:cards_show
bind:generate_runners /> bind:generate_runners />
</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> <tr>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span <span
on:click={() => { on:click={() => {
const newstate = !current_runners.some((r) => r.is_selected === true); const newstate = !current_runners.some((r) => r.is_selected === true);
current_runners = current_runners.map((r) => { current_runners = current_runners.map((r) => {
r.is_selected = newstate; r.is_selected = newstate;
return r; return r;
}); });
}} }}
class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)} class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)}
{$_('deselect-all')} {$_('deselect-all')}
{:else}{$_('select-all')}{/if} {:else}{$_('select-all')}{/if}
</span> </span>
</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">
{$_('contact-information')} {$_('contact-information')}
</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">
{$_('group')} {$_('group')}
</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">
{$_('distance-in-km')} {$_('distance-in-km')}
</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_runners as runner} {#each current_runners as runner}
{#if runner.firstname {#if runner.firstname
.toLowerCase() .toLowerCase()
.includes( .includes(
searchvalue.toLowerCase() searchvalue.toLowerCase()
) || runner.lastname ) || runner.lastname
.toLowerCase() .toLowerCase()
.includes( .includes(
searchvalue.toLowerCase() searchvalue.toLowerCase()
) || should_display_based_on_id(runner.id)} ) || should_display_based_on_id(runner.id)}
{#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0} {#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0}
<tr <tr
data-rowid="user_{runner.id}" data-rowid="user_{runner.id}"
data-groupid={runner.group.id}> data-groupid={runner.group.id}>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<input <input
bind:checked={runner.is_selected} bind:checked={runner.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="ml-4"> <div class="ml-4">
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{runner.firstname} {runner.firstname}
{runner.middlename || ''} {runner.middlename || ''}
{runner.lastname} {runner.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">
{#if runner.email} {#if runner.email}
<div class="text-sm text-gray-500">{runner.email}</div> <div class="text-sm text-gray-500">{runner.email}</div>
{/if} {/if}
{#if runner.phone} {#if runner.phone}
<div class="text-sm text-gray-500">{runner.phone}</div> <div class="text-sm text-gray-500">{runner.phone}</div>
{/if} {/if}
{#if runner.address.address1 !== null} {#if runner.address.address1 !== null}
{runner.address.address1}<br /> {runner.address.address1}<br />
{runner.address.address2 || ''}<br /> {runner.address.address2 || ''}<br />
{runner.address.postalcode} {runner.address.postalcode}
{runner.address.city} {runner.address.city}
{runner.address.country} {runner.address.country}
{/if} {/if}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
{#if runner.group.responseType === 'RUNNERTEAM'} {#if runner.group.responseType === 'RUNNERTEAM'}
<a <a
href="../teams/{runner.group.id}" href="../teams/{runner.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
{/if} {/if}
{#if runner.group.responseType === 'RUNNERORGANIZATION'} {#if runner.group.responseType === 'RUNNERORGANIZATION'}
<a <a
href="../orgs/{runner.group.id}" href="../orgs/{runner.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
{/if} {/if}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
{runner.distance} {runner.distance}
</td> </td>
{#if active_deletes[runner.id] === true} {#if active_deletes[runner.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[runner.id] = false; active_deletes[runner.id] = false;
}} }}
tabindex="0" 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 <button
on:click={() => { on:click={() => {
RunnerService.runnerControllerRemove(runner.id, true) RunnerService.runnerControllerRemove(runner.id, true)
.then((resp) => { .then((resp) => {
current_runners = current_runners.filter((obj) => obj.id !== runner.id); current_runners = current_runners.filter((obj) => obj.id !== runner.id);
}) })
.catch((err) => {}); .catch((err) => {});
}} }}
tabindex="0" 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> </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="./{runner.id}" href="./{runner.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
<button <button
on:click={() => { on:click={() => {
active_deletes[runner.id] = true; active_deletes[runner.id] = true;
}} }}
tabindex="0" 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} {/if}
</td> </td>
{/if} {/if}
</tr> </tr>
{/if} {/if}
{/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,297 +1,297 @@
<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 Toastify from "toastify-js"; import Toastify from "toastify-js";
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";
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;
$: 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) => {
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) => {
Toastify({ Toastify({
text: "Organization deleted", text: "Organization deleted",
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
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) {
Toastify({ Toastify({
text: "updating team", text: "updating team",
duration: 2500, duration: 2500,
}).showToast(); }).showToast();
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;
Toastify({ Toastify({
text: "updated team", text: "updated team",
duration: 2500, duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
}) })
.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="mb-8 text-3xl font-extrabold leading-tight"> <div class="mb-8 text-3xl font-extrabold leading-tight">
{original.name} {original.name}
<span data-id="org_actions_${teamdata.id}"> <span data-id="org_actions_${teamdata.id}">
<GenerateSponsoringContracts <GenerateSponsoringContracts
bind:sponsoring_contracts_show bind:sponsoring_contracts_show
bind:generate_teams /> bind:generate_teams />
<GenerateRunnerCards <GenerateRunnerCards
bind:cards_show bind:cards_show
bind:generate_teams /> bind:generate_teams />
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
<button <button
on:click={() => { on:click={() => {
import_modal_open = true; import_modal_open = true;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('import-runners')} {$_('import-runners')}
</button> </button>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
{#if delete_triggered} {#if delete_triggered}
<button <button
on:click={deleteTeam} on:click={deleteTeam}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button>
<button <button
on:click={() => { on:click={() => {
delete_triggered = !delete_triggered; delete_triggered = !delete_triggered;
}} }}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
{/if} {/if}
{#if !delete_triggered} {#if !delete_triggered}
<button <button
on:click={() => { on:click={() => {
delete_triggered = true; delete_triggered = true;
}} }}
type="button" type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-team')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-team')}</button>
{/if} {/if}
{/if} {/if}
{#if !delete_triggered} {#if !delete_triggered}
<button <button
on:click={submit} on:click={submit}
disabled={!save_enabled} disabled={!save_enabled}
class:opacity-50={!save_enabled} class:opacity-50={!save_enabled}
type="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:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{/if} {/if}
</span> </span>
</div> </div>
<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="mr-2 flex items-center"> <li class="mr-2 flex items-center">
<svg <svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="h-3 w-3 stroke-current" class="h-3 w-3 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><path xmlns="http://www.w3.org/2000/svg"><path
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" /></svg> <polyline points="9 22 9 12 15 12 15 22" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="/">Home</a><svg <a class="mr-2" href="/">Home</a><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"><line
x1="5" x1="5"
y1="12" y1="12"
x2="19" x2="19"
y2="12" /> y2="12" />
<polyline points="12 5 19 12 12 19" /></svg> <polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="mr-2 flex items-center"> <li class="mr-2 flex items-center">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2" class="flex-shrink-0 w-5 h-5 mr-2"
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"><path viewBox="0 0 640 512"><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" /></svg> 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>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="./">Teams</a><svg <a class="mr-2" href="./">Teams</a><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"><line
x1="5" x1="5"
y1="12" y1="12"
x2="19" x2="19"
y2="12" /> y2="12" />
<polyline points="12 5 19 12 12 19" /></svg> <polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="mr-2">Team-Details #{params.teamid}</span> <span class="mr-2">Team-Details #{params.teamid}</span>
</li> </li>
</ol> </ol>
</nav> </nav>
</div> </div>
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label for="name" class="font-medium text-gray-700">Name</label> <label for="name" class="font-medium text-gray-700">Name</label>
<input <input
autocomplete="off" autocomplete="off"
placeholder="Name" placeholder="Name"
type="text" type="text"
bind:value={teamdata.name} bind:value={teamdata.name}
name="name" name="name"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label
for="contact" for="contact"
class="font-medium text-gray-700">{$_('contact')}</label> class="font-medium text-gray-700">{$_('contact')}</label>
<Select <Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => label itemFilter={(label, filterText, option) => label
.toLowerCase() .toLowerCase()
.includes( .includes(
filterText.toLowerCase() filterText.toLowerCase()
) || option.value.id ) || option.value.id
.toString() .toString()
.startsWith(filterText.toLowerCase())} .startsWith(filterText.toLowerCase())}
items={contacts} items={contacts}
showChevron={true} showChevron={true}
placeholder={$_('no-contact-selected')} placeholder={$_('no-contact-selected')}
noOptionsMessage={$_('no-contact-found')} noOptionsMessage={$_('no-contact-found')}
bind:selectedValue={contact} bind:selectedValue={contact}
on:select={(selectedValue) => (teamdata.contact = selectedValue.detail.value)} on:select={(selectedValue) => (teamdata.contact = selectedValue.detail.value)}
on:clear={() => (teamdata.contact = null)} /> on:clear={() => (teamdata.contact = null)} />
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label
for="org" for="org"
class="font-medium text-gray-700">{$_('organization')}</label> class="font-medium text-gray-700">{$_('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-gray-500 rounded-md p-2" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => label itemFilter={(label, filterText, option) => label
.toLowerCase() .toLowerCase()
.includes( .includes(
filterText.toLowerCase() filterText.toLowerCase()
) || option.id.value ) || option.id.value
.toString() .toString()
.startsWith(filterText.toLowerCase())} .startsWith(filterText.toLowerCase())}
items={orgs} items={orgs}
showChevron={true} showChevron={true}
placeholder={$_('search-for-an-organization-by-name-or-id')} placeholder={$_('search-for-an-organization-by-name-or-id')}
noOptionsMessage={$_('no-organizations-found')} noOptionsMessage={$_('no-organizations-found')}
bind:selectedValue={group} bind:selectedValue={group}
on:select={(selectedValue) => (teamdata.parentGroup = selectedValue.detail.value)} on:select={(selectedValue) => (teamdata.parentGroup = selectedValue.detail.value)}
on:clear={() => (teamdata.parentGroup = null)} /> on:clear={() => (teamdata.parentGroup = null)} />
</div> </div>
</section> </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

@ -1,217 +1,217 @@
<script> <script>
import { getLocaleFromNavigator, t, _ } from "svelte-i18n"; import { getLocaleFromNavigator, t, _ } from "svelte-i18n";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
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 { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
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";
$: 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( $: cards_show = current_teams.some(
(r) => r.is_selected === true (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="gridjs-input gridjs-search-input mb-4" /> class="gridjs-input gridjs-search-input mb-4" />
<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 <GenerateRunnerCards
bind:cards_show bind:cards_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> <tr>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span <span
on:click={() => { on:click={() => {
const newstate = !current_teams.some((r) => r.is_selected === true); const newstate = !current_teams.some((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">{#if current_teams.some((r) => r.is_selected === true)} class="underline cursor-pointer select-none">{#if current_teams.some((r) => r.is_selected === true)}
{$_('deselect-all')} {$_('deselect-all')}
{:else}{$_('select-all')}{/if} {:else}{$_('select-all')}{/if}
</span> </span>
</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 data-rowid="team_{t.id}"> <tr 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="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.name} {t.name}
</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 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.parentGroup} {#if t.parentGroup}
<a <a
href="../orgs/{t.parentGroup.id}" href="../orgs/{t.parentGroup.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.parentGroup.name}</a> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.parentGroup.name}</a>
{:else}{$_('no-organization-specified')}{/if} {:else}{$_('no-organization-specified')}{/if}
</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 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.contact} {#if t.contact}
<a <a
href="../contacts/{t.contact.id}" href="../contacts/{t.contact.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.contact.firstname} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.contact.firstname}
{t.contact.middlename || ''} {t.contact.middlename || ''}
{t.contact.lastname}</a> {t.contact.lastname}</a>
{:else}{$_('no-contact-specified')}{/if} {:else}{$_('no-contact-specified')}{/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">Cancel class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel
Delete</button> Delete</button>
<button <button
on:click={() => { on:click={() => {
RunnerTeamService.runnerTeamControllerRemove(t.id, false) RunnerTeamService.runnerTeamControllerRemove(t.id, false)
.then((resp) => { .then((resp) => {
current_teams = current_teams.filter((obj) => obj.id !== t.id); current_teams = current_teams.filter((obj) => obj.id !== t.id);
Toastify({ Toastify({
text: $_('organization-deleted'), text: $_('organization-deleted'),
duration: 500, duration: 500,
backgroundColor: backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)', 'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast(); }).showToast();
}) })
.catch((err) => { .catch((err) => {
modal_open = true; modal_open = true;
delete_team = t; delete_team = t;
}); });
}} }}
tabindex="0" 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> </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">{$_('details')}</a> class="text-indigo-600 hover:text-indigo-900">{$_('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">{$_('delete')}</button> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('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}