This commit is contained in:
Philipp Dormann 2025-03-20 21:42:02 +01:00
parent 3d3ce2918b
commit e82350df4a
Signed by: philipp
GPG Key ID: 3BB9ADD52DCA4314
8 changed files with 669 additions and 681 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -218,7 +218,7 @@
</label> </label>
<a <a
href="../tracks" href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 border border-current"
>{editable.track.name} >{editable.track.name}
</a> </a>
</div> </div>

View File

@ -111,7 +111,7 @@
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
<a <a
href="../tracks" href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 border border-current"
> >
{s.track.name || s.track.distance + "m"}</a {s.track.name || s.track.distance + "m"}</a
> >

View File

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

View File

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