Compare commits

..

No commits in common. "dev" and "1.13.4" have entirely different histories.
dev ... 1.13.4

37 changed files with 478 additions and 944 deletions

View File

@ -2,53 +2,9 @@
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.
#### [1.14.2](https://git.odit.services/lfk/frontend/compare/1.14.1...1.14.2)
- feat(GenerateRunnerCertificates): support skipping runners without scans [`5a7bc23`](https://git.odit.services/lfk/frontend/commit/5a7bc239d2f93ced9ebdc5b113fe27d0d8d3899c)
#### [1.14.1](https://git.odit.services/lfk/frontend/compare/1.14.0...1.14.1)
> 26 May 2025
- fix: ensure numeric values are parsed as integers in DocumentServer methods [`1b088b8`](https://git.odit.services/lfk/frontend/commit/1b088b87bf6e67796c2509d9c21f21833cb4df0f)
- chore(release): 1.14.1 [`661a698`](https://git.odit.services/lfk/frontend/commit/661a698fbaeb2432bec758ed632a520676ae86b2)
#### [1.14.0](https://git.odit.services/lfk/frontend/compare/1.13.5...1.14.0)
> 20 May 2025
- wip [`564a971`](https://git.odit.services/lfk/frontend/commit/564a971c63403af2e2eb550db814519576d62023)
- wip [`50b5e4e`](https://git.odit.services/lfk/frontend/commit/50b5e4e455ce705fc5ef7f3d069d88c9ff48a6af)
- wip [`2c91f46`](https://git.odit.services/lfk/frontend/commit/2c91f463758c8452561fbcc5dad8412edba8915d)
- wip [`1386b80`](https://git.odit.services/lfk/frontend/commit/1386b80d0c8569cf127f8235b3dd249c2775594a)
- wip [`6ef6dc0`](https://git.odit.services/lfk/frontend/commit/6ef6dc007837c237273a29ca489ef0cdb92f7c6c)
- wip [`3709881`](https://git.odit.services/lfk/frontend/commit/370988117683ab1fdc149a30f920cc6a66575c7a)
- chore(release): 1.14.0 [`d5fecd3`](https://git.odit.services/lfk/frontend/commit/d5fecd3f31916b80c305d76f37c4600f1d242eba)
- wip [`77413c7`](https://git.odit.services/lfk/frontend/commit/77413c7e5350a1d8643d2baf135b531235f78e64)
- wip [`0cb1193`](https://git.odit.services/lfk/frontend/commit/0cb1193269912b047abfacb6012463093c2adcfa)
- wip [`9ef3435`](https://git.odit.services/lfk/frontend/commit/9ef34359d8ac32674c28825b91b6aa2877e63552)
- wip [`a00af08`](https://git.odit.services/lfk/frontend/commit/a00af08b3f7c8278cfc54af6f593a9dcf4509ab4)
- wip [`286bd61`](https://git.odit.services/lfk/frontend/commit/286bd614976dcf8bcb14cffd092f23ef65393917)
- wip [`b89d4f2`](https://git.odit.services/lfk/frontend/commit/b89d4f248c5575548d77336832c64dc6e395efc3)
- inputElementID param [`4d79589`](https://git.odit.services/lfk/frontend/commit/4d79589903bb0726f6bcb2c0e5089a9e20f7db17)
- wip [`53f5fa3`](https://git.odit.services/lfk/frontend/commit/53f5fa3988e81215e17e41b7dd92e9ddf897610a)
- wip [`444b1f5`](https://git.odit.services/lfk/frontend/commit/444b1f537016b303a57fcaaac4468a749fe4f33c)
- disable autocomplete [`72e5425`](https://git.odit.services/lfk/frontend/commit/72e5425c0847102b0ed3f88abe17dc22ccea0a30)
#### [1.13.5](https://git.odit.services/lfk/frontend/compare/1.13.4...1.13.5)
> 20 May 2025
- add missing cursor-pointer [`6500839`](https://git.odit.services/lfk/frontend/commit/650083965a35cf3b05b6b67389ff8035dc5fa3fa)
- refactor(DonationsOverview): drop checkboxes - they dont do anything [`06d22c9`](https://git.odit.services/lfk/frontend/commit/06d22c929f94587d9bdbcb4abfc0a770cf94a771)
- chore(release): 1.13.5 [`e2a1c9a`](https://git.odit.services/lfk/frontend/commit/e2a1c9a508c6061e55438afefcd641e3d9423aaa)
#### [1.13.4](https://git.odit.services/lfk/frontend/compare/1.13.3...1.13.4) #### [1.13.4](https://git.odit.services/lfk/frontend/compare/1.13.3...1.13.4)
> 20 May 2025
- feat(donationcreate): improved focus handling [`a827279`](https://git.odit.services/lfk/frontend/commit/a82727916345c7e713d4225c4771ef3f23d1392c) - feat(donationcreate): improved focus handling [`a827279`](https://git.odit.services/lfk/frontend/commit/a82727916345c7e713d4225c4771ef3f23d1392c)
- chore(release): 1.13.4 [`bbf659e`](https://git.odit.services/lfk/frontend/commit/bbf659e52d249732fadb659fdbd24a89d2e8ec42)
- chore(deps): remove unused [`3842d8b`](https://git.odit.services/lfk/frontend/commit/3842d8b1048ce12f0f70bf3d0530590470f0d200) - chore(deps): remove unused [`3842d8b`](https://git.odit.services/lfk/frontend/commit/3842d8b1048ce12f0f70bf3d0530590470f0d200)
- fix(donationcreate): clearing [`9298a0d`](https://git.odit.services/lfk/frontend/commit/9298a0dc922ee5ed5b7c9017c865ad4b68fca3c8) - fix(donationcreate): clearing [`9298a0d`](https://git.odit.services/lfk/frontend/commit/9298a0dc922ee5ed5b7c9017c865ad4b68fca3c8)
- feat(donationcreate): autofocus runner input on page load [`b9e2e65`](https://git.odit.services/lfk/frontend/commit/b9e2e653310c686bc06b9f27c38b49e9c6a3eaef) - feat(donationcreate): autofocus runner input on page load [`b9e2e65`](https://git.odit.services/lfk/frontend/commit/b9e2e653310c686bc06b9f27c38b49e9c6a3eaef)

View File

@ -13,7 +13,7 @@
<body> <body>
<span style="display: none; visibility: hidden" id="buildinfo" <span style="display: none; visibility: hidden" id="buildinfo"
>RELEASE_INFO-1.14.2-RELEASE_INFO</span >RELEASE_INFO-1.13.4-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>

View File

@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "1.14.2", "version": "1.13.4",
"type": "module", "type": "module",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",

View File

@ -180,7 +180,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -189,7 +189,7 @@
edit_modal_open = false; edit_modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -103,7 +103,7 @@
<button <button
on:click={submit} on:click={submit}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("delete")} {$_("delete")}
</button> </button>
@ -112,7 +112,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -469,7 +469,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -194,7 +194,7 @@
payment_modal_open = false; payment_modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -102,7 +102,7 @@
<button <button
on:click={submit} on:click={submit}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("delete")} {$_("delete")}
</button> </button>
@ -111,7 +111,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -1,28 +1,26 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import TableActions from "../shared/TableActions.svelte"; import TableActions from "../shared/TableActions.svelte";
export let detailsLink; export let detailsLink;
export let detailsAction; export let detailsAction;
export let deleteEnabled; export let deleteEnabled;
export let deleteAction; export let deleteAction;
export let paymentAction; export let paymentAction;
</script> </script>
{#if paymentAction} {#if paymentAction}
<button <button
on:click={paymentAction} on:click={paymentAction}
class="text-[#025a21] cursor-pointer hover:text-green-900 mr-4" class="text-[#025a21] hover:text-green-900 mr-4">{$_("enter-payment")}</button
>{$_("enter-payment")}</button >
>
{:else} {:else}
<span class="inline-block opacity-0 cursor-default mr-4" style="" <span class="inline-block opacity-0 cursor-default mr-4" style="">{$_("enter-payment")}</span>
>{$_("enter-payment")}</span
>
{/if} {/if}
<TableActions <TableActions
bind:detailsAction bind:detailsAction
bind:detailsLink bind:detailsLink
bind:deleteAction bind:deleteAction
bind:deleteEnabled bind:deleteEnabled
/> />

View File

@ -247,6 +247,14 @@
<thead class="border-b border-gray-400"> <thead class="border-b border-gray-400">
{#each $table.getHeaderGroups() as headerGroup} {#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none"> <tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<InputElement
type="checkbox"
checked={$table.getIsAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()}
/>
</th>
{#each headerGroup.headers as header} {#each headerGroup.headers as header}
<TableHeader {header} /> <TableHeader {header} />
{/each} {/each}
@ -256,6 +264,13 @@
<tbody> <tbody>
{#each $table.getRowModel().rows as row} {#each $table.getRowModel().rows as row}
<tr class="odd:bg-white even:bg-gray-100"> <tr class="odd:bg-white even:bg-gray-100">
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement
type="checkbox"
checked={row.getIsSelected()}
on:change={() => row.toggleSelected()}
/>
</td>
{#each row.getVisibleCells() as cell} {#each row.getVisibleCells() as cell}
<td> <td>
<svelte:component <svelte:component

View File

@ -433,7 +433,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -72,14 +72,14 @@
<button <button
on:click={deleteDonor} on:click={deleteDonor}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("confirm-delete-donor-with-all-donations")} {$_("confirm-delete-donor-with-all-donations")}
</button> </button>
<button <button
on:click={cancelDelete} on:click={cancelDelete}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel-keep-donor")} {$_("cancel-keep-donor")}
</button> </button>

View File

@ -174,7 +174,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -294,7 +294,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -86,14 +86,14 @@
<button <button
on:click={deleteOrg} on:click={deleteOrg}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("confirm-delete-organization-and-associated-teams-runners")} {$_("confirm-delete-organization-and-associated-teams-runners")}
</button> </button>
<button <button
on:click={cancelDelete} on:click={cancelDelete}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel-keep-organization")} {$_("cancel-keep-organization")}
</button> </button>

View File

@ -1,4 +1,3 @@
class DocumentServer { class DocumentServer {
baseUrl: string; baseUrl: string;
apiKey: string; apiKey: string;
@ -13,19 +12,19 @@ class DocumentServer {
for (let i = 0; i < cards.length; i++) { for (let i = 0; i < cards.length; i++) {
const card = { const card = {
id: parseInt(cards[i].id), id: cards[i].id,
enabled: cards[i].enabled, enabled: cards[i].enabled,
code: cards[i].code, code: cards[i].code,
runner: { runner: {
id: parseInt(cards[i]?.runner?.id), id: cards[i]?.runner?.id,
first_name: cards[i]?.runner?.firstname, first_name: cards[i]?.runner?.firstname,
middle_name: cards[i]?.runner?.middlename, middle_name: cards[i]?.runner?.middlename,
last_name: cards[i]?.runner?.lastname, last_name: cards[i]?.runner?.lastname,
group: { group: {
id: parseInt(cards[i]?.runner?.group?.id), id: cards[i]?.runner?.group.id,
name: cards[i]?.runner?.group.name, name: cards[i]?.runner?.group.name,
parent_group: { parent_group: {
id: parseInt(cards[i]?.runner?.group?.parentGroup?.id), id: cards[i]?.runner?.group?.parentGroup?.id,
name: cards[i]?.runner?.group?.parentGroup?.name, name: cards[i]?.runner?.group?.parentGroup?.name,
}, },
}, },
@ -58,15 +57,15 @@ class DocumentServer {
for (let i = 0; i < runners.length; i++) { for (let i = 0; i < runners.length; i++) {
console.log(runners[i]); console.log(runners[i]);
const card = { const card = {
id: parseInt(runners[i].id), id: runners[i].id,
first_name: runners[i].firstname, first_name: runners[i].firstname,
middle_name: runners[i].middlename, middle_name: runners[i].middlename,
last_name: runners[i].lastname, last_name: runners[i].lastname,
group: { group: {
id: parseInt(runners[i].group.id), id: runners[i].group.id,
name: runners[i].group.name, name: runners[i].group.name,
parent_group: { parent_group: {
id: parseInt(runners[i]?.group?.parentGroup?.id), id: runners[i]?.group?.parentGroup?.id,
name: runners[i]?.group?.parentGroup?.name, name: runners[i]?.group?.parentGroup?.name,
}, },
}, },
@ -97,28 +96,28 @@ class DocumentServer {
for (let i = 0; i < runners.length; i++) { for (let i = 0; i < runners.length; i++) {
const certificate = { const certificate = {
id: parseInt(runners[i].id), id: runners[i].id,
first_name: runners[i].firstname, first_name: runners[i].firstname,
middle_name: runners[i].middlename, middle_name: runners[i].middlename,
last_name: runners[i].lastname, last_name: runners[i].lastname,
self_service_link: runners[i].selfserviceLink, self_service_link: runners[i].selfserviceLink,
group: { group: {
id: parseInt(runners[i].group.id), id: runners[i].group.id,
name: runners[i].group.name, name: runners[i].group.name,
parent_group: { parent_group: {
id: parseInt(runners[i]?.group?.parentGroup?.id), id: runners[i]?.group?.parentGroup?.id,
name: runners[i]?.group?.parentGroup?.name, name: runners[i]?.group?.parentGroup?.name,
}, },
}, },
distance: parseInt(runners[i].distance), distance: runners[i].distance,
distance_donations: runners[i].distanceDonations.map( distance_donations: runners[i].distanceDonations.map(
(distanceDonation: any) => { (distanceDonation: any) => {
return { return {
id: distanceDonation.id, id: distanceDonation.id,
amount: parseInt(distanceDonation.amount), amount: distanceDonation.amount,
amount_per_distance: parseInt(distanceDonation.amountPerDistance), amount_per_distance: distanceDonation.amountPerDistance,
donor: { donor: {
id: parseInt(distanceDonation.donor.id), id: distanceDonation.donor.id,
first_name: distanceDonation.donor.firstname, first_name: distanceDonation.donor.firstname,
middle_name: distanceDonation.donor.middlename, middle_name: distanceDonation.donor.middlename,
last_name: distanceDonation.donor.lastname, last_name: distanceDonation.donor.lastname,

View File

@ -20,13 +20,13 @@
export let generate_orgs = []; export let generate_orgs = [];
export let generate_teams = []; export let generate_teams = [];
function generateCertificates(locale, include0runners = false) { function generateCertificates(locale) {
if (generate_orgs.length > 0) { if (generate_orgs.length > 0) {
generateOrgCertificates(locale, include0runners = false); generateOrgCertificates(locale);
} else if (generate_teams.length > 0) { } else if (generate_teams.length > 0) {
generateTeamCertificates(locale, include0runners = false); generateTeamCertificates(locale);
} else { } else {
generateRunnerCertificates(locale, include0runners = false); generateRunnerCertificates(locale);
} }
} }
function download(blob, fileName) { function download(blob, fileName) {
@ -41,7 +41,7 @@
toast.success($_("pdf-successfully-generated")); toast.success($_("pdf-successfully-generated"));
} }
async function generateRunnerCertificates(locale, include0runners = false) { async function generateRunnerCertificates(locale) {
toast.loading($_("generating-pdf")); toast.loading($_("generating-pdf"));
const current_donations = const current_donations =
(await DonationService.donationControllerGetAll()) || []; (await DonationService.donationControllerGetAll()) || [];
@ -50,15 +50,7 @@
const linkRunner = await RunnerService.runnerControllerGetOne(runner.id) const linkRunner = await RunnerService.runnerControllerGetOne(runner.id)
linkRunner.distanceDonations = linkRunner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || []; current_donations.filter((d) => d.runner?.id == runner.id) || [];
// check if linkRunner.distance is 0, if so, and include0runners is false, skip this runner certificateRunners.push(linkRunner);
if (
!include0runners &&
(linkRunner.distance === 0 || linkRunner.distance === null)
) {
continue;
} else {
certificateRunners.push(linkRunner);
}
} }
documentServer documentServer
.generateCertificates(certificateRunners, locale) .generateCertificates(certificateRunners, locale)
@ -74,7 +66,7 @@
.catch((err) => {}); .catch((err) => {});
} }
async function generateTeamCertificates(locale, include0runners = false) { async function generateTeamCertificates(locale) {
toast.loading($_("generating-pdfs")); toast.loading($_("generating-pdfs"));
let count = 0; let count = 0;
const current_donations = const current_donations =
@ -88,15 +80,7 @@
for (let runner of runners) { for (let runner of runners) {
runner.distanceDonations = runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || []; current_donations.filter((d) => d.runner?.id == runner.id) || [];
// check if runner.distance is 0, if so, and include0runners is false, skip this runner certificateRunners.push(runner);
if (
!include0runners &&
(runner.distance === 0 || runner.distance === null)
) {
continue;
} else {
certificateRunners.push(runner);
}
} }
documentServer documentServer
.generateCertificates(certificateRunners, locale) .generateCertificates(certificateRunners, locale)
@ -111,7 +95,7 @@
} }
} }
async function generateOrgCertificates(locale, include0runners = false) { async function generateOrgCertificates(locale) {
toast.loading($_("generating-pdfs")); toast.loading($_("generating-pdfs"));
const current_donations = const current_donations =
(await DonationService.donationControllerGetAll()) || []; (await DonationService.donationControllerGetAll()) || [];
@ -130,15 +114,7 @@
for (let runner of runners) { for (let runner of runners) {
runner.distanceDonations = runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || []; current_donations.filter((d) => d.runner?.id == runner.id) || [];
// check if runner.distance is 0, if so, and include0runners is false, skip this runner certificateRunners.push(runner);
if (
!include0runners &&
(runner.distance === 0 || runner.distance === null)
) {
continue;
} else {
certificateRunners.push(runner);
}
} }
await documentServer await documentServer
.generateCertificates(certificateRunners, locale) .generateCertificates(certificateRunners, locale)
@ -185,36 +161,20 @@
</script> </script>
{#if certificates_show} {#if certificates_show}
<button <button
on:click={() => { on:click={() => {
generateCertificates("de", true); generateCertificates("de");
}} }}
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:w-auto sm:text-sm mb-1 lg:mb-0" 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:w-auto sm:text-sm mb-1 lg:mb-0"
> >
{$_("generate-runner-certificates")}: DE {$_("generate-runner-certificates")}: DE
</button> </button>
<button <button
on:click={() => { on:click={() => {
generateCertificates("de", false); generateCertificates("en");
}} }}
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:w-auto sm:text-sm mb-1 lg:mb-0" 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:w-auto sm:text-sm mb-1 lg:mb-0"
> >
{$_("generate-runner-certificates")}: DE [{$_('exclude_0m_runners_certificate')}] {$_("generate-runner-certificates")}: EN
</button> </button>
<button
on:click={() => {
generateCertificates("en", true);
}}
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:w-auto sm:text-sm mb-1 lg:mb-0"
>
{$_("generate-runner-certificates")}: EN
</button>
<button
on:click={() => {
generateCertificates("en", false);
}}
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:w-auto sm:text-sm mb-1 lg:mb-0"
>
{$_("generate-runner-certificates")}: EN [{$_('exclude_0m_runners_certificate')}]
</button>
{/if} {/if}

View File

@ -338,7 +338,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -90,7 +90,7 @@
<button <button
on:click={submit} on:click={submit}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("delete")} {$_("delete")}
</button> </button>
@ -99,7 +99,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -261,7 +261,7 @@
cancelModal(); cancelModal();
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>
@ -375,7 +375,7 @@
cancelModal(); cancelModal();
}} }}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -195,7 +195,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -90,7 +90,7 @@
<button <button
on:click={submit} on:click={submit}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("delete")} {$_("delete")}
</button> </button>
@ -99,7 +99,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -203,7 +203,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -82,14 +82,14 @@
<button <button
on:click={deleteStation} on:click={deleteStation}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("confirm-delete-station-with-all-scans")} {$_("confirm-delete-station-with-all-scans")}
</button> </button>
<button <button
on:click={cancelDelete} on:click={cancelDelete}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel-keep-station")} {$_("cancel-keep-station")}
</button> </button>

View File

@ -85,14 +85,14 @@
<button <button
on:click={deleteMe} on:click={deleteMe}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("confirm-delete-my-user-profile")} {$_("confirm-delete-my-user-profile")}
</button> </button>
<button <button
on:click={cancelDelete} on:click={cancelDelete}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel-keep-my-profile")} {$_("cancel-keep-my-profile")}
</button> </button>

View File

@ -148,7 +148,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -81,14 +81,14 @@
<button <button
on:click={deleteClient} on:click={deleteClient}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("confirm-delete-statsclient")} {$_("confirm-delete-statsclient")}
</button> </button>
<button <button
on:click={cancelDelete} on:click={cancelDelete}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel-keep-statsclient")} {$_("cancel-keep-statsclient")}
</button> </button>

View File

@ -199,7 +199,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -85,14 +85,14 @@
<button <button
on:click={deleteTeam} on:click={deleteTeam}
type="button" type="button"
class="confirm_deletion_button" class="w-full inline-flex 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"
> >
{$_("confirm-delete-team-and-associated-runners")} {$_("confirm-delete-team-and-associated-runners")}
</button> </button>
<button <button
on:click={cancelDelete} on:click={cancelDelete}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel-keep-team")} {$_("cancel-keep-team")}
</button> </button>

View File

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

View File

@ -1,357 +0,0 @@
<script>
import { createEventDispatcher, onMount, tick } from "svelte";
// Generate a default unique ID
function generateDefaultID() {
return "virtual-select-" + Math.random().toString(36).slice(2);
}
// Props
export let options = [];
export let selected = null;
export let inputPlaceholder = "Search options...";
export let noOptionsText = "No options found";
export let inputAriaLabel = "Search and select an option";
export let toggleAriaLabel = "Toggle dropdown";
export let clearAriaLabel = "Clear selection";
export let filterFn = null; // Custom filter function
export let autofocus = false; // Autofocus input
export let inputElementID = generateDefaultID(); // Input element ID
// Internal state
let searchTerm = "";
let filteredOptions = options;
let isOpen = false;
let container;
let visibleItems = [];
let startIndex = 0;
let itemHeight = 40; // Fixed height for each option (in pixels)
let visibleCount = 10; // Default number of items to render
let focusedIndex = -1; // Track the focused option index (-1 means no focus)
let inputElement; // Reference to input element
const dispatch = createEventDispatcher();
// Filter options based on search term
$: {
filteredOptions = searchTerm
? filterFn
? options.filter((option) => filterFn(option, searchTerm))
: options.filter((option) =>
option.label.toLowerCase().includes(searchTerm.toLowerCase())
)
: options;
// Reset scroll and focus when filtered options change
startIndex = 0;
focusedIndex = -1;
updateVisibleItems();
}
// Update visible items based on scroll position
function updateVisibleItems() {
if (!container) return;
const scrollTop = container.scrollTop;
startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + visibleCount,
filteredOptions.length
);
visibleItems = filteredOptions.slice(startIndex, endIndex);
}
// Handle scroll event
function handleScroll() {
updateVisibleItems();
}
// Calculate visible item count based on container height
async function updateVisibleCount() {
if (container) {
await tick(); // Wait for DOM to render
visibleCount = Math.ceil(container.clientHeight / itemHeight) + 2; // Buffer of 2 items
updateVisibleItems();
}
}
// Handle option selection
function selectOption(option) {
selected = option.value;
isOpen = false;
searchTerm = option.label; // Set searchTerm to the selected option's label
focusedIndex = -1;
dispatch("onSelected", option.value);
}
// Handle clear selection
function clearSelection() {
selected = null;
searchTerm = "";
focusedIndex = -1;
dispatch("onSelected", null);
dispatch("onClear");
}
// Reset component state
export function reset() {
selected = null;
searchTerm = "";
isOpen = false;
focusedIndex = -1;
startIndex = 0;
updateVisibleItems();
dispatch("onSelected", null);
dispatch("onClear");
}
// Toggle dropdown
async function toggleDropdown() {
isOpen = !isOpen;
if (isOpen) {
forceVisibleItems();
}
}
// Handle click outside to close dropdown
function handleClickOutside(event) {
if (!event.target.closest(".select-container")) {
isOpen = false;
focusedIndex = -1;
}
}
// Handle input focus to open dropdown
async function handleInputFocus() {
// forceVisibleItems();
}
// Handle input typing to open dropdown
async function handleInput() {
forceVisibleItems();
}
async function forceVisibleItems() {
isOpen = true;
await updateVisibleCount(); // Ensure items render on focus
// these 2 timeouts are a more or less tmp fix for rendering items when dropdown opens
setTimeout(async () => {
await updateVisibleCount(); // Ensure items render on focus
}, 25);
setTimeout(async () => {
await updateVisibleCount(); // Ensure items render on focus
}, 50);
}
// Handle keyboard navigation
function handleKeydown(event, index) {
if (!isOpen) return;
if (event.key === "ArrowDown") {
event.preventDefault();
if (focusedIndex < filteredOptions.length - 1) {
focusedIndex += 1;
scrollToFocusedItem();
}
} else if (event.key === "ArrowUp") {
event.preventDefault();
if (focusedIndex > 0) {
focusedIndex -= 1;
scrollToFocusedItem();
} else if (focusedIndex === -1 && filteredOptions.length > 0) {
focusedIndex = 0;
scrollToFocusedItem();
}
} else if (event.key === "Enter" && index >= 0) {
event.preventDefault();
selectOption(filteredOptions[index]);
} else if (event.key === "Escape") {
event.preventDefault();
isOpen = false;
focusedIndex = -1;
}
}
// Scroll to the focused item
function scrollToFocusedItem() {
if (!container || focusedIndex < 0) return;
const itemTop = focusedIndex * itemHeight;
const itemBottom = itemTop + itemHeight;
const containerTop = container.scrollTop;
const containerBottom = containerTop + container.clientHeight;
if (itemTop < containerTop) {
container.scrollTop = itemTop;
} else if (itemBottom > containerBottom) {
container.scrollTop = itemBottom - container.clientHeight;
}
updateVisibleItems();
}
// Initialize container size observer and autofocus fallback
onMount(async () => {
if (container) {
const resizeObserver = new ResizeObserver(updateVisibleCount);
resizeObserver.observe(container);
return () => resizeObserver.disconnect();
}
// Fallback autofocus with tick to ensure inputElement is bound
if (autofocus && inputElement) {
await tick();
inputElement.focus();
}
});
// Get display text for the input
function getDisplayText() {
if (!selected) return inputPlaceholder;
const selectedOption = options.find((option) => option.value === selected);
return selectedOption ? selectedOption.label : inputPlaceholder;
}
</script>
<svelte:window on:click={handleClickOutside} />
<div class="select-container relative w-full">
<!-- Select element with inline search -->
<div
class="border rounded-md px-3 py-2 bg-white shadow-sm flex items-center gap-2"
role="combobox"
aria-expanded={isOpen}
>
<input
autocomplete="off"
type="text"
id={inputElementID}
bind:value={searchTerm}
bind:this={inputElement}
placeholder={getDisplayText()}
class="w-full bg-transparent focus:outline-none {selected
? 'text-black'
: 'text-gray-700'}"
{autofocus}
on:focus={handleInputFocus}
on:input={handleInput}
on:keydown={(e) => {
if (e.key === "Enter" && !isOpen) {
toggleDropdown();
} else {
handleKeydown(e, focusedIndex);
}
}}
aria-label={inputAriaLabel}
/>
{#if selected}
<button
type="button"
class="w-5 h-5 flex items-center justify-center text-gray-500 hover:text-gray-700"
on:click={clearSelection}
on:keydown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
clearSelection();
} else if (e.key === "Escape") {
e.preventDefault();
isOpen = false;
focusedIndex = -1;
}
}}
role="button"
tabindex="0"
aria-label={clearAriaLabel}
>
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
{/if}
<svg
class="w-4 h-4 text-gray-500 transform {isOpen ? 'rotate-180' : ''}"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
on:click={toggleDropdown}
role="button"
tabindex="0"
on:keydown={(e) => {
if (e.key === "Enter") toggleDropdown();
else if (e.key === "Escape") {
e.preventDefault();
isOpen = false;
focusedIndex = -1;
}
}}
aria-label={toggleAriaLabel}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke
Politeness="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</div>
<!-- Dropdown -->
{#if isOpen}
<div
class="absolute z-10 w-full mt-1 bg-white border rounded-md shadow-lg max-h-80 overflow-auto"
bind:this={container}
on:scroll={handleScroll}
role="listbox"
>
{#if filteredOptions.length > 0}
<!-- Virtualized list container -->
<div style="height: {filteredOptions.length * itemHeight}px;">
<div style="transform: translateY({startIndex * itemHeight}px);">
{#each visibleItems as item, i (item.label + "-" + (startIndex + i))}
<div
class="px-3 py-2 hover:bg-blue-100 cursor-pointer {selected ===
item.value
? 'bg-blue-50'
: ''} {focusedIndex === startIndex + i
? 'bg-blue-200 outline outline-2 outline-blue-500'
: ''}"
on:click={() => selectOption(item)}
on:keydown={(e) => handleKeydown(e, startIndex + i)}
role="option"
tabindex="0"
aria-selected={selected === item.value}
>
{item.label}
</div>
{/each}
</div>
</div>
{:else}
<div class="px-3 py-2 text-gray-500">{noOptionsText}</div>
{/if}
</div>
{/if}
</div>
<style>
/* Ensure Tailwind classes handle additional styling */
:global(.select-container input:focus) {
border-color: #3b82f6; /* Tailwind's blue-500 */
}
:global([role="option"]:focus) {
outline: 2px solid #3b82f6;
outline-offset: -2px;
}
:global([role="button"]:focus) {
outline: 2px solid #3b82f6;
outline-offset: -2px;
}
</style>

View File

@ -230,7 +230,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -287,7 +287,7 @@
modal_open = false; modal_open = false;
}} }}
type="button" type="button"
class="cancel_modal_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"
> >
{$_("cancel")} {$_("cancel")}
</button> </button>

View File

@ -227,12 +227,11 @@
"enabled_large": "Aktiviert", "enabled_large": "Aktiviert",
"english": "Englisch", "english": "Englisch",
"enter-payment": "Zahlung eingeben", "enter-payment": "Zahlung eingeben",
"error-creating-donation": "Fehler beim Erstellen des Sponsorings", "error-creating-donation": "Fehler bei der Anlage",
"error-during-import": "Fehler beim Importieren", "error-during-import": "Fehler beim Importieren",
"error-whyile-copying-to-clipboard": "Fehler beim Kopieren in die Zwischenablage", "error-whyile-copying-to-clipboard": "Fehler beim Kopieren in die Zwischenablage",
"error_on_login": "😢Fehler beim Login", "error_on_login": "😢Fehler beim Login",
"everything-concerning-your-profile": "Alles zu deinem Profil", "everything-concerning-your-profile": "Alles zu deinem Profil",
"exclude_0m_runners_certificate": "ohne 0m Läufer",
"existing-donor": "Existierende Sponsor:in", "existing-donor": "Existierende Sponsor:in",
"faq": "FAQ", "faq": "FAQ",
"fast_card_replacement": "Karten-Schnellzusweisung (Mit Mobilgeräteunterstützung)", "fast_card_replacement": "Karten-Schnellzusweisung (Mit Mobilgeräteunterstützung)",

View File

@ -227,12 +227,10 @@
"enabled_large": "Enabled", "enabled_large": "Enabled",
"english": "English", "english": "English",
"enter-payment": "Enter payment", "enter-payment": "Enter payment",
"error-creating-donation": "error creating the sponsoring",
"error-during-import": "Error during import", "error-during-import": "Error during import",
"error-whyile-copying-to-clipboard": "Error while copying to clipboard", "error-whyile-copying-to-clipboard": "Error while copying to clipboard",
"error_on_login": "Error on login", "error_on_login": "Error on login",
"everything-concerning-your-profile": "Everything concerning your profile", "everything-concerning-your-profile": "Everything concerning your profile",
"exclude_0m_runners_certificate": "exclude runners without scans",
"existing-donor": "Existing Donor", "existing-donor": "Existing Donor",
"faq": "FAQ", "faq": "FAQ",
"fast_card_replacement": "Fast card replacement (with mobile support)", "fast_card_replacement": "Fast card replacement (with mobile support)",

View File

@ -31,9 +31,3 @@
.donation_active_tab { .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; @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;
} }
.confirm_deletion_button {
@apply w-full cursor-pointer inline-flex 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;
}
.cancel_modal_button {
@apply w-full cursor-pointer 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;
}