403 lines
12 KiB
Svelte
403 lines
12 KiB
Svelte
<script>
|
|
import { _ } from "svelte-i18n";
|
|
import {
|
|
DonationService,
|
|
DonorService,
|
|
RunnerService,
|
|
} from "@odit/lfk-client-js";
|
|
import toast from "svelte-french-toast";
|
|
import VirtualSelect from "./VirtualSelect.svelte";
|
|
import { onMount } from "svelte";
|
|
|
|
let runners = [];
|
|
let donors = [];
|
|
let runnerinfo = { id: 0, firstname: "", lastname: "" };
|
|
let donorinfo = { id: 0, firstname: "", lastname: "" };
|
|
let address = {
|
|
address1: "",
|
|
address2: "",
|
|
city: "",
|
|
postalcode: "",
|
|
country: "Germany",
|
|
};
|
|
let amount = null;
|
|
let address_checked = false;
|
|
let donor_create_new = false;
|
|
let last_created = null;
|
|
|
|
RunnerService.runnerControllerGetAll()
|
|
.then((val) => {
|
|
runners = val.map((r) => {
|
|
return { label: getRunnerLabel(r), value: r };
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
console.log("error fetching runners:", err);
|
|
});
|
|
|
|
function loadDonors() {
|
|
DonorService.donorControllerGetAll()
|
|
.then((val) => {
|
|
donors = val.map((r) => {
|
|
return { label: getRunnerLabel(r), value: r };
|
|
});
|
|
console.log("refreshed donors");
|
|
setTimeout(() => {
|
|
loadDonors;
|
|
}, 30000);
|
|
})
|
|
.catch((err) => {
|
|
console.log("error fetching donors:", err);
|
|
});
|
|
}
|
|
loadDonors();
|
|
|
|
const getRunnerLabel = (option) => {
|
|
return (
|
|
[option.firstname, option.middlename, option.lastname]
|
|
.join(" ")
|
|
.replace(" ", " ") +
|
|
" [#" +
|
|
option.id +
|
|
"]"
|
|
);
|
|
};
|
|
|
|
let selectRefRunner;
|
|
let selectRefDonor;
|
|
|
|
function resetAll() {
|
|
runnerinfo = { id: 0, firstname: "", lastname: "" };
|
|
donorinfo = { id: 0, firstname: "", lastname: "" };
|
|
amount = 0;
|
|
address_checked = false;
|
|
donor_create_new = false;
|
|
selectRefRunner.reset();
|
|
selectRefDonor.reset();
|
|
document.querySelector("#jjqzqicxujrnnh1x3447x18x").focus();
|
|
}
|
|
onMount(() => {
|
|
document.querySelector("#jjqzqicxujrnnh1x3447x18x").focus();
|
|
});
|
|
</script>
|
|
|
|
<div class="p-4">
|
|
<h3 class="text-3xl font-bold">{$_("fast_donation_create")}</h3>
|
|
<!-- -->
|
|
<div>
|
|
<div class="w-full space-y-4 mb-6">
|
|
{#if last_created}
|
|
<div class="mt-4 p-3 bg-green-50 border border-green-200 rounded-md">
|
|
<p class="text-black">
|
|
{$_("last-created-donation")}: #{last_created.id}: {last_created.amountPerDistance /
|
|
100}€ für {getRunnerLabel(last_created.runner)} von {getRunnerLabel(
|
|
last_created.donor
|
|
)}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- -->
|
|
<h4 class="text-xl font-semibold">{$_("runner")}</h4>
|
|
<VirtualSelect
|
|
inputElementID="jjqzqicxujrnnh1x3447x18x"
|
|
bind:this={selectRefRunner}
|
|
on:onClear={() => {
|
|
console.log("Cleared selection");
|
|
}}
|
|
options={runners}
|
|
filterFn={(item, searchTerm) => {
|
|
if (searchTerm.startsWith("#")) {
|
|
const id = parseInt(searchTerm.replace("#", ""));
|
|
return item.value.id === id;
|
|
}
|
|
return item.label.toLowerCase().includes(searchTerm.toLowerCase());
|
|
}}
|
|
bind:selected={runnerinfo}
|
|
inputAriaLabel={$_("search-for-runner-by-name-or-id")}
|
|
inputPlaceholder={$_("search-for-runner-by-name-or-id")}
|
|
noOptionsText={$_("no-runners-found")}
|
|
on:onSelected={(data) => {
|
|
if (data.detail !== null) {
|
|
document.querySelector("#donation_amount_eur").focus();
|
|
}
|
|
}}
|
|
/>
|
|
|
|
<!-- Amount Input -->
|
|
<div>
|
|
<h4 class="text-xl font-semibold">{$_("amount-per-kilometer")}</h4>
|
|
<div class="mt-1 flex rounded-md shadow-sm">
|
|
<input
|
|
autocomplete="off"
|
|
class:border-red-500={!amount > 0}
|
|
class:focus:border-red-500={!amount > 0}
|
|
class:focus:ring-red-500={!amount > 0}
|
|
bind:value={amount}
|
|
on:keydown={(e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
document.querySelector("#button_existing_donor").focus();
|
|
}
|
|
}}
|
|
type="number"
|
|
step="0.01"
|
|
id="donation_amount_eur"
|
|
name="donation_amount_eur"
|
|
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 p-2"
|
|
placeholder="z.B. 1,50"
|
|
/>
|
|
<span
|
|
class="inline-flex items-center px-3 rounded-r-md border border-neutral-300 bg-neutral-50 text-neutral-500 text-sm"
|
|
>€</span
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Donor Selection -->
|
|
<div>
|
|
<h4 class="text-xl font-semibold">{$_("donor")}</h4>
|
|
|
|
<!-- Donor Type Toggle -->
|
|
<div class="mb-2">
|
|
<div class="flex border rounded-md overflow-hidden shadow-sm">
|
|
<button
|
|
on:keydown={(e) => {
|
|
if (e.key === "ArrowRight") {
|
|
e.preventDefault();
|
|
document.querySelector("#button_new_donor").focus();
|
|
document.querySelector("#button_new_donor").click();
|
|
}
|
|
}}
|
|
id="button_existing_donor"
|
|
class:bg-indigo-600={!donor_create_new}
|
|
class:text-white={!donor_create_new}
|
|
class="py-2 px-4 w-1/2 transition-colors"
|
|
on:click={() => {
|
|
donor_create_new = false;
|
|
donorinfo = { id: 0, firstname: "", lastname: "" };
|
|
}}
|
|
>
|
|
{$_("existing-donor")}
|
|
</button>
|
|
<button
|
|
on:keydown={(e) => {
|
|
if (e.key === "ArrowLeft") {
|
|
e.preventDefault();
|
|
document.querySelector("#button_existing_donor").focus();
|
|
document.querySelector("#button_existing_donor").click();
|
|
}
|
|
}}
|
|
id="button_new_donor"
|
|
class={`py-2 px-4 w-1/2 transition-colors ${donor_create_new ? "bg-indigo-600 text-white" : "bg-gray-100 text-gray-700"}`}
|
|
on:click={() => {
|
|
donor_create_new = true;
|
|
donorinfo = { id: 0, firstname: "", lastname: "" };
|
|
}}
|
|
>
|
|
{$_("new-donor")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{#if !donor_create_new}
|
|
<VirtualSelect
|
|
inputElementID="zt12c3udy3bme5bqobmqcif1"
|
|
bind:this={selectRefDonor}
|
|
on:onClear={() => {
|
|
console.log("Cleared selection");
|
|
}}
|
|
options={donors}
|
|
filterFn={(item, searchTerm) => {
|
|
return item.label
|
|
.toLowerCase()
|
|
.includes(searchTerm.toLowerCase());
|
|
}}
|
|
bind:selected={donorinfo}
|
|
inputAriaLabel={$_("search-for-donor")}
|
|
inputPlaceholder={$_("search-for-donor")}
|
|
noOptionsText={$_("no-donors-found")}
|
|
on:onSelected={(data) => {
|
|
if (data.detail !== null) {
|
|
document.querySelector("#submit_button").focus();
|
|
}
|
|
}}
|
|
/>
|
|
{:else}
|
|
<div class="space-y-3">
|
|
<!-- First Name -->
|
|
<div>
|
|
<label
|
|
for="firstname"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>
|
|
{$_("first-name")}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="firstname"
|
|
bind:value={donorinfo.firstname}
|
|
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={$_("first-name")}
|
|
/>
|
|
</div>
|
|
|
|
<!-- Last Name -->
|
|
<div>
|
|
<label
|
|
for="lastname"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>
|
|
{$_("last-name")}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="lastname"
|
|
bind:value={donorinfo.lastname}
|
|
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={$_("last-name")}
|
|
/>
|
|
</div>
|
|
|
|
<!-- Address Checkbox -->
|
|
<div class="flex items-start mt-4">
|
|
<div class="flex items-center h-5">
|
|
<input
|
|
id="address_check"
|
|
type="checkbox"
|
|
bind:checked={address_checked}
|
|
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
|
/>
|
|
</div>
|
|
<div class="ml-3 text-sm">
|
|
<label for="address_check" class="font-medium text-gray-700">
|
|
{$_("receipt-needed")}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
{#if address_checked}
|
|
<!-- Address Fields -->
|
|
<div
|
|
class="space-y-3 mt-3 p-3 border border-gray-200 rounded-md bg-gray-50"
|
|
>
|
|
<div>
|
|
<label
|
|
for="address1"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>
|
|
{$_("address")}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="address1"
|
|
bind:value={address.address1}
|
|
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"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
for="address2"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>
|
|
{$_("apartment-suite-etc")}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="address2"
|
|
bind:value={address.address2}
|
|
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"
|
|
/>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label
|
|
for="postalcode"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>
|
|
{$_("zip-postal-code")}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="postalcode"
|
|
bind:value={address.postalcode}
|
|
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"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
for="city"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>
|
|
{$_("city")}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="city"
|
|
bind:value={address.city}
|
|
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"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
<!-- Submit Button -->
|
|
<div class="mt-6">
|
|
<button
|
|
id="submit_button"
|
|
type="button"
|
|
class="w-full inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:bg-gray-400 disabled:cursor-not-allowed"
|
|
disabled={!amount > 0 ||
|
|
!runnerinfo.id ||
|
|
(!donorinfo.id && !donor_create_new) ||
|
|
(donor_create_new &&
|
|
(!donorinfo.firstname || !donorinfo.lastname)) ||
|
|
(donor_create_new &&
|
|
address_checked &&
|
|
(!address.address1 || !address.city || !address.postalcode))}
|
|
on:click={async () => {
|
|
if (donor_create_new) {
|
|
donorinfo = await DonorService.donorControllerPost({
|
|
firstname: donorinfo.firstname,
|
|
lastname: donorinfo.lastname,
|
|
receiptNeeded: address_checked,
|
|
...(address_checked ? { address: address } : {}),
|
|
});
|
|
}
|
|
|
|
DonationService.donationControllerPostDistance({
|
|
donor: donorinfo.id,
|
|
runner: runnerinfo.id,
|
|
amountPerDistance: amount * 100,
|
|
})
|
|
.then((data) => {
|
|
last_created = data;
|
|
toast.success($_("donation-created-successfully"));
|
|
resetAll();
|
|
loadDonors();
|
|
})
|
|
.catch((err) => {
|
|
console.error("Error creating donation:", err);
|
|
toast.error($_("error-creating-donation"));
|
|
});
|
|
}}
|
|
>
|
|
{$_("create")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
:global(:root) {
|
|
--sv-bg: #ffffff;
|
|
}
|
|
</style>
|