This commit is contained in:
Philipp Dormann 2025-04-28 10:24:07 +02:00
parent 8cb6093f0b
commit a21f61f3f3
Signed by: philipp
GPG Key ID: 3BB9ADD52DCA4314
2 changed files with 391 additions and 328 deletions

View File

@ -1,340 +1,397 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { import {
DonationService, DonationService,
DonorService, DonorService,
RunnerService, RunnerService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Select from "svelte-select"; import Select from "svelte-select";
import { createEventDispatcher, onMount } from "svelte"; import { createEventDispatcher, onMount } from "svelte";
import toast from "svelte-french-toast"; import toast from "svelte-french-toast";
export let modal_open; export let modal_open;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const getDonorLabel = (option) => const getDonorLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterDonors = (label, filterText, option) => const filterDonors = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) || label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.id.toString().startsWith(filterText.toLowerCase()); option.value.id.toString().startsWith(filterText.toLowerCase());
$: donor = 0; $: donor = 0;
$: runner = 0; $: runner = 0;
$: donors = []; $: donors = [];
$: runners = []; $: runners = [];
$: is_fixed = false; $: type = "distance";
$: is_paid = false; $: is_fixed = false;
$: amount_input = 0; $: is_paid = false;
$: processed_last_submit = true; $: amount_input = 0;
$: is_amount_valid = amount_input > 0; $: processed_last_submit = true;
$: createbtnenabled = is_amount_valid; $: is_amount_valid = amount_input > 0;
(() => { $: createbtnenabled = is_amount_valid;
document.onkeydown = (e) => { (() => {
e = e || window.event; document.onkeydown = (e) => {
if (e.key === "Escape") { e = e || window.event;
modal_open = false; if (e.key === "Escape") {
} modal_open = false;
if (e.keyCode === 13) { }
if (createbtnenabled === true) { if (e.keyCode === 13) {
createbtnenabled = false; if (createbtnenabled === true) {
submit(); createbtnenabled = false;
} submit();
} }
}; }
})(); };
function submit() { })();
if (processed_last_submit === true) { function submit() {
let amount_cent = Math.floor(amount_input * 100); if (processed_last_submit === true) {
processed_last_submit = false; let amount_cent = Math.floor(amount_input * 100);
toast.loading($_("adding-donation")); processed_last_submit = false;
if (is_fixed) { toast.loading($_("adding-donation"));
let postdata = { if (is_fixed) {
donor, let postdata = {
amount: amount_cent, donor,
paidAmount: 0, amount: amount_cent,
}; paidAmount: 0,
if (is_paid) { };
postdata.paidAmount = amount_cent; if (is_paid) {
} postdata.paidAmount = amount_cent;
DonationService.donationControllerPostFixed(postdata) }
.then((result) => { DonationService.donationControllerPostFixed(postdata)
donor = donors[0].id || 0; .then((result) => {
runner = runners[0].id || 0; donor = donors[0].id || 0;
amount_input = 0; runner = runners[0].id || 0;
modal_open = false; amount_input = 0;
// modal_open = false;
toast.dismiss(); //
toast.success($_("donation_added")); toast.dismiss();
dispatch("created", { donations: [result] }); toast.success($_("donation_added"));
}) dispatch("created", { donations: [result] });
.catch((err) => { })
// .catch((err) => {
}) //
.finally(() => { })
processed_last_submit = true; .finally(() => {
}); processed_last_submit = true;
} else { });
let postdata = { } else {
donor, let postdata = {
runner, donor,
amountPerDistance: amount_cent, runner,
}; amountPerDistance: amount_cent,
DonationService.donationControllerPostDistance(postdata) };
.then((result) => { DonationService.donationControllerPostDistance(postdata)
donor = donors[0].id || 0; .then((result) => {
runner = runners[0].id || 0; donor = donors[0].id || 0;
amount_input = 0; runner = runners[0].id || 0;
modal_open = false; amount_input = 0;
// modal_open = false;
toast.dismiss(); //
toast.success($_("donation_added")); toast.dismiss();
dispatch("created", { donations: [result] }); toast.success($_("donation_added"));
}) dispatch("created", { donations: [result] });
.catch((err) => { })
// .catch((err) => {
}) //
.finally(() => { })
processed_last_submit = true; .finally(() => {
}); processed_last_submit = true;
} });
} }
} }
}
onMount(async () => { onMount(async () => {
donors = (await DonorService.donorControllerGetAll()).map( donors = (await DonorService.donorControllerGetAll()).map((r) => {
(r) => { return { label: getDonorLabel(r), value: r };
return { label: getDonorLabel(r), value: r }; });
} runners = (await RunnerService.runnerControllerGetAll()).map((r) => {
); return { label: getDonorLabel(r), value: r };
runners = (await RunnerService.runnerControllerGetAll()).map( });
(r) => { });
return { label: getDonorLabel(r), value: r };
}
);
});
</script> </script>
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-hidden" class="fixed z-10 inset-0 overflow-y-hidden"
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;
}} }}
> >
<div <div
class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4" class="flex items-end justify-center h-screen text-center sm:block p-0 lg:p-4"
> >
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div <div
class="absolute inset-0 bg-gray-500 opacity-75" class="absolute inset-0 bg-neutral-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 text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw] relative z-10" class="inline-block align-bottom text-left shadow-xl transform transition-all sm:align-middle w-full lg:w-auto min-w-auto lg:min-w-[35vw] relative z-10"
role="dialog" 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 rounded-t-xl"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 rounded-t-xl">
<div class=""> <div class="">
<div <div
class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10" class="flex-shrink-0 flex items-center justify-center size-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
> >
<svg <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" height="24"
><path fill="none" d="M0 0h24v24H0z" /> ><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg /></svg
> >
</div> </div>
<div class="mt-3"> <div class="mt-3">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-neutral-900">
{#if is_fixed} Sponsoring erstellen
{$_("create-a-new-fixed-donation")} </h3>
{:else}{$_("create-a-new-distance-donation")}{/if} <nav
</h3> class="relative z-0 flex border border-neutral-200 rounded-xl overflow-hidden mb-2"
<label class="content-center align-middle object-center"> >
<span class="text-base" class:text-gray-300={is_fixed} <button
>{$_("distance-donation")}</span on:click={() => {
> type = "distance";
<input }}
class="toggle relative w-10 h-5 transition-all duration-200 ease-in-out bg-gray-400 rounded-full shadow-inner outline-none appearance-none align-middle" type="button"
type="checkbox" id="bar-with-underline-item-1"
bind:checked={is_fixed} class:donation_active_tab={type === "distance"}
/> class:donation_inactive_tab={type !== "distance"}
<span class="ml-2 text-base" class:text-gray-300={!is_fixed} aria-selected={type === "distance"}
>{$_("fixed-donation")}</span role="tab"
> >
</label> Spende pro km
<div class="mb-6"> </button>
<p class="text-sm text-gray-500"> <button
{$_( on:click={() => {
"please-provide-the-nessecary-information-to-create-a-new-donation" type = "fixed";
)} }}
</p> type="button"
</div> id="bar-with-underline-item-2"
<div class="grid grid-cols-6 gap-2 lg:gap-6 text-left"> class:donation_active_tab={type === "fixed"}
<div class="col-span-6"> class:donation_inactive_tab={type !== "fixed"}
<label aria-selected={type === "fixed"}
for="donor" role="tab"
class="block text-sm font-medium text-gray-700" >
>{$_("donor")}</label Festbetrag
> </button>
<Select <button
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" on:click={() => {
itemFilter={(label, filterText, option) => type = "anonymous";
filterDonors(label, filterText, option)} }}
items={donors} type="button"
showChevron={true} id="bar-with-underline-item-3"
placeholder={$_("search-for-donor-name-or-id")} class:donation_active_tab={type === "anonymous"}
noOptionsMessage={$_("no-donors-found")} class:donation_inactive_tab={type !== "anonymous"}
on:select={(selectedValue) => aria-selected={type === "anonymous"}
(donor = selectedValue.detail.value.id)} role="tab"
on:clear={() => (donors = null)} >
/> Anonyme Spende
</div> </button>
{#if !is_fixed} </nav>
<div class="col-span-6">
<label <div class="grid grid-cols-6 gap-2 lg:gap-6 text-left">
for="donor" {#if type === "anonymous"}
class="block text-sm font-medium text-gray-700" <div class="col-span-6">
>{$_("runner")}</label <label
> for="donation_amount_eur"
<Select class="block text-sm font-medium text-neutral-900"
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" >
itemFilter={(label, filterText, option) => {$_("donation-amount")}</label
filterDonors(label, filterText, option)} >
items={runners} <div class="mt-1 flex rounded-md shadow-sm">
showChevron={true} <input
placeholder={$_("search-for-runner-by-name-or-id")} autocomplete="off"
noOptionsMessage={$_("no-runners-found")} class:border-red-500={!is_amount_valid}
on:select={(selectedValue) => class:focus:border-red-500={!is_amount_valid}
(runner = selectedValue.detail.value.id)} class:focus:ring-red-500={!is_amount_valid}
on:clear={() => (runner = null)} bind:value={amount_input}
/> type="number"
</div> step="0.01"
{/if} name="donation_amount_eur"
<div class="col-span-6"> class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 p-2"
<label placeholder="2.00"
for="donation_amount_eur" />
class="block text-sm font-medium text-gray-700" <span
> class="inline-flex items-center px-3 rounded-r-md border border-neutral-300 bg-neutral-50 text-neutral-500 text-sm"
{#if !is_fixed} >€</span
{$_("amount-per-kilometer")} >
{:else}{$_("donation-amount")}{/if}</label </div>
> {#if !is_amount_valid}
<div class="mt-1 flex rounded-md shadow-sm"> <span
<input class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
autocomplete="off" >
class:border-red-500={!is_amount_valid} {$_("donation-amount-must-be-greater-that-0-00eur")}
class:focus:border-red-500={!is_amount_valid} </span>
class:focus:ring-red-500={!is_amount_valid} {/if}
bind:value={amount_input} </div>
type="number" {:else}
step="0.01" <div class="col-span-6">
name="donation_amount_eur" <label
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 p-2" for="donor"
placeholder="2.00" class="block text-sm font-medium text-neutral-900"
/> >{$_("donor")}</label
<span >
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm" <Select
>€</span containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 rounded-md p-2"
> itemFilter={(label, filterText, option) =>
</div> filterDonors(label, filterText, option)}
{#if !is_amount_valid} items={donors}
<span showChevron={true}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1" placeholder={$_("search-for-donor-name-or-id")}
> noOptionsMessage={$_("no-donors-found")}
{$_("donation-amount-must-be-greater-that-0-00eur")} on:select={(selectedValue) =>
</span> (donor = selectedValue.detail.value.id)}
{/if} on:clear={() => (donors = null)}
</div> />
{#if is_fixed} </div>
<div class="col-span-6"> {#if !is_fixed}
<label <div class="col-span-6">
for="paid" <label
class="block text-sm font-medium text-gray-700" for="donor"
>{$_("already-paid")}</label class="block text-sm font-medium text-neutral-900"
> >{$_("runner")}</label
<p class="text-gray-500"> >
<input <Select
id="paid" containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 rounded-md p-2"
bind:checked={is_paid} itemFilter={(label, filterText, option) =>
name="paid" filterDonors(label, filterText, option)}
type="checkbox" items={runners}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" showChevron={true}
/> placeholder={$_("search-for-runner-by-name-or-id")}
<span class="align-text-bottom"> noOptionsMessage={$_("no-runners-found")}
{#if is_paid} on:select={(selectedValue) =>
{$_("paid")} (runner = selectedValue.detail.value.id)}
{:else} on:clear={() => (runner = null)}
{$_("open")} />
{/if} </div>
</span> {/if}
</p> <div class="col-span-6">
</div> <label
{/if} for="donation_amount_eur"
</div> class="block text-sm font-medium text-neutral-900"
</div> >
</div> {#if !is_fixed}
</div> {$_("amount-per-kilometer")}
<div class="bg-gray-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10"> {:else}{$_("donation-amount")}{/if}</label
<button >
disabled={!createbtnenabled} <div class="mt-1 flex rounded-md shadow-sm">
class:opacity-50={!createbtnenabled} <input
on:click={submit} autocomplete="off"
type="button" class:border-red-500={!is_amount_valid}
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" class:focus:border-red-500={!is_amount_valid}
> class:focus:ring-red-500={!is_amount_valid}
{$_("create")} bind:value={amount_input}
</button> type="number"
<button step="0.01"
on:click={() => { name="donation_amount_eur"
modal_open = false; class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 p-2"
}} placeholder="2.00"
type="button" />
class="w-full 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 hidden lg:block" <span
> class="inline-flex items-center px-3 rounded-r-md border border-neutral-300 bg-neutral-50 text-neutral-500 text-sm"
{$_("cancel")} >€</span
</button> >
</div> </div>
</div> {#if !is_amount_valid}
</div> <span
</div> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("donation-amount-must-be-greater-that-0-00eur")}
</span>
{/if}
</div>
{#if is_fixed}
<div class="col-span-6">
<label
for="paid"
class="block text-sm font-medium text-neutral-900"
>{$_("already-paid")}</label
>
<p class="text-neutral-500">
<input
id="paid"
bind:checked={is_paid}
name="paid"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-neutral-300 rounded"
/>
<span class="align-text-bottom">
{#if is_paid}
{$_("paid")}
{:else}
{$_("open")}
{/if}
</span>
</p>
</div>
{/if}
{/if}
</div>
</div>
</div>
</div>
<div
class="bg-neutral-50 px-4 lg:py-3 sm:px-6 grid gap-2 lg:rounded-b-xl pt-3 pb-10"
>
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{$_("create")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="w-full justify-center rounded-md border border-neutral-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-neutral-900 hover:bg-neutral-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 hidden lg:block"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if} {/if}
<style> <style>
.toggle:before { .toggle:before {
content: ""; content: "";
position: absolute; position: absolute;
width: 1.25rem; width: 1.25rem;
height: 1.25rem; height: 1.25rem;
border-radius: 50%; border-radius: 50%;
top: 0; top: 0;
left: 0; left: 0;
transform: scale(1.1); transform: scale(1.1);
box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.2); box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.2);
background-color: white; background-color: white;
transition: 0.2s ease-in-out; transition: 0.2s ease-in-out;
} }
.toggle:checked { .toggle:checked {
/* @apply: bg-indigo-400; */ /* @apply: bg-indigo-400; */
background-color: #7f9cf5; background-color: #7f9cf5;
} }
.toggle:checked:before { .toggle:checked:before {
left: 1.25rem; left: 1.25rem;
} }
</style> </style>

View File

@ -25,3 +25,9 @@
#html5-qrcode-button-camera-stop { #html5-qrcode-button-camera-stop {
@apply px-2 inline-flex text-lg leading-5 font-semibold rounded-md border border-current bg-red-100 text-red-800 mb-2 cursor-pointer; @apply px-2 inline-flex text-lg leading-5 font-semibold rounded-md border border-current bg-red-100 text-red-800 mb-2 cursor-pointer;
} }
.donation_inactive_tab {
@apply min-w-0 flex-1 bg-white first:border-s-0 border-s border-b-2 border-neutral-200 py-4 px-4 text-neutral-800 hover:text-neutral-700 text-sm font-medium text-center overflow-hidden hover:bg-neutral-200 cursor-pointer focus:z-10 focus:outline-hidden focus:bg-neutral-200 disabled:opacity-50 disabled:pointer-events-none;
}
.donation_active_tab {
@apply min-w-0 flex-1 bg-blue-400 text-white first:border-s-0 border-s border-b-2 border-neutral-200 py-4 px-4 text-sm font-medium text-center overflow-hidden cursor-pointer focus:outline-hidden;
}