feat(tools): Added tool for fast sponsoring creation
This commit is contained in:
		| @@ -105,6 +105,26 @@ | ||||
|  | ||||
|           <span>{$_("scanclient")}</span> | ||||
|         </a> | ||||
|         <a | ||||
|           class:activenav={$router.path.includes("/tools/donationcreate/")} | ||||
|           class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" | ||||
|           href="/tools/donationcreate/" | ||||
|         > | ||||
|           <svg | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 24 24" | ||||
|             fill="currentColor" | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|           > | ||||
|             <path | ||||
|               fill-rule="evenodd" | ||||
|               d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z" | ||||
|               clip-rule="evenodd" | ||||
|             /> | ||||
|           </svg> | ||||
|  | ||||
|           <span>{$_("donation-quick-add")}</span> | ||||
|         </a> | ||||
|         <h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase"> | ||||
|           {$_("management")} | ||||
|         </h2> | ||||
|   | ||||
							
								
								
									
										300
									
								
								src/components/tools/DonationCreate.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								src/components/tools/DonationCreate.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,300 @@ | ||||
| <script> | ||||
|   import { _ } from "svelte-i18n"; | ||||
|   import { | ||||
|     DonationService, | ||||
|     DonorService, | ||||
|     RunnerService, | ||||
|   } from "@odit/lfk-client-js"; | ||||
|   import Svelecte from "svelecte"; | ||||
|   import Select from "svelte-select"; | ||||
|   import toast from "svelte-french-toast"; | ||||
|  | ||||
|   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 = 0; | ||||
|   let lastname = ""; | ||||
|   let address_checked = false; | ||||
|  | ||||
|   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: getDonorlabel(r), value: r }; | ||||
|         }); | ||||
|         console.log("refreshed donors"); | ||||
|         setTimeout(() => { | ||||
|           loadDonors; | ||||
|         }, 30000); | ||||
|       }) | ||||
|       .catch((err) => { | ||||
|         console.log("error fetching donors:", err); | ||||
|       }); | ||||
|   } | ||||
|   loadDonors(); | ||||
|  | ||||
|   const getRunnerLabel = (option) => | ||||
|     option.firstname + " " + (option.middlename || "") + " " + option.lastname; | ||||
|  | ||||
|   const getDonorlabel = (option) => `${option.firstname} (${option.lastname})`; | ||||
|  | ||||
|   const filterRunners = (label, filterText, option) => { | ||||
|     if (filterText.startsWith("#")) { | ||||
|       return option.value.id == parseInt(filterText.replace("#", "")); | ||||
|     } | ||||
|     return ( | ||||
|       label.toLowerCase().includes(filterText.toLowerCase()) || | ||||
|       option.value.toString().startsWith(filterText.toLowerCase()) | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   function resetAll() { | ||||
|     runnerinfo = { id: 0, firstname: "", lastname: "" }; | ||||
|     donorinfo = { id: 0, firstname: "", lastname: "" }; | ||||
|     amount = 0; | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <div class="p-4"> | ||||
|   <h3 class="text-3xl font-bold">{$_("fast_donation_create")}</h3> | ||||
|   <!--  --> | ||||
|   <div class="grid grid-cols-6 gap-4"> | ||||
|     <div class="col-span-2"> | ||||
|       <h4 class="text-xl font-semibold"> | ||||
|         {$_("runner")} | ||||
|       </h4> | ||||
|       <Select | ||||
|         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) => | ||||
|           filterRunners(label, filterText, option)} | ||||
|         items={runners} | ||||
|         showChevron={true} | ||||
|         placeholder={$_("search-for-runner-by-name-or-id")} | ||||
|         noOptionsMessage={$_("no-runners-found")} | ||||
|         on:select={(selectedValue) => { | ||||
|           runnerinfo = selectedValue.detail.value; | ||||
|         }} | ||||
|         on:clear={() => (runnerinfo.runner = null)} | ||||
|       /> | ||||
|     </div> | ||||
|     <div class="col-span-2"> | ||||
|       <h4 class="text-xl font-semibold"> | ||||
|         {$_("donor")} | ||||
|       </h4> | ||||
|       <div class="mb-2"> | ||||
|         <Svelecte | ||||
|           name="donor_fistname" | ||||
|           placeholder={$_("first-name")} | ||||
|           clearable={true} | ||||
|           options={donors} | ||||
|           keepCreated={false} | ||||
|           creatable={true} | ||||
|           labelField="label" | ||||
|           on:change={(e) => { | ||||
|             if (!e.detail?.value) { | ||||
|               donorinfo = { id: 0, firstname: "", lastname: "" }; | ||||
|               return; | ||||
|             } | ||||
|             if (!e.detail?.$created) { | ||||
|               donorinfo = e.detail.value; | ||||
|               lastname = e.detail.value.lastname; | ||||
|             } else { | ||||
|               console.log("created option", e); | ||||
|               donorinfo.firstname = e.detail.value; | ||||
|             } | ||||
|           }} | ||||
|           class="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-0.5" | ||||
|         /> | ||||
|         <input | ||||
|           autocomplete="off" | ||||
|           placeholder={$_("last-name")} | ||||
|           class:border-red-500={donorinfo.lastname?.length == 0} | ||||
|           class:focus:border-red-500={donorinfo.lastname?.length == 0} | ||||
|           class:focus:ring-red-500={donorinfo.lastname?.length == 0} | ||||
|           bind:value={lastname} | ||||
|           on:input={e => { | ||||
|             donorinfo.lastname = e.target.value; | ||||
|           }} | ||||
|           type="text" | ||||
|           name="lastname" | ||||
|           class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" | ||||
|         /> | ||||
|       </div> | ||||
|       <div class="flex items-start"> | ||||
|         <div class="flex items-center h-5"> | ||||
|           {#if donorinfo.id == 0} | ||||
|             <input | ||||
|               bind:checked={address_checked} | ||||
|               id="comments" | ||||
|               name="comments" | ||||
|               type="checkbox" | ||||
|               class="focus:ring-indigo-500 size-4 text-indigo-600 border-gray-300 rounded" | ||||
|             /> | ||||
|           {:else} | ||||
|             <input | ||||
|               checked={true} | ||||
|               disabled | ||||
|               id="comments" | ||||
|               name="comments" | ||||
|               type="checkbox" | ||||
|               class="focus:ring-indigo-500 size-4 text-indigo-600 border-gray-300 rounded" | ||||
|             /> | ||||
|           {/if} | ||||
|         </div> | ||||
|         <div class="ml-3 text-sm"> | ||||
|           <label for="comments" class="font-semibold text-gray-700" | ||||
|             >{$_("receipt-needed")}</label | ||||
|           > | ||||
|         </div> | ||||
|       </div> | ||||
|       {#if address_checked} | ||||
|         <div class="col-span-6"> | ||||
|           <label for="address1" class="block text-sm font-medium text-gray-700" | ||||
|             >{$_("address")}</label | ||||
|           > | ||||
|           <input | ||||
|             autocomplete="off" | ||||
|             placeholder="Address" | ||||
|             class:border-red-500={address.address1.length == 0} | ||||
|             class:focus:border-red-500={address.address1.length == 0} | ||||
|             class:focus:ring-red-500={address.address1.length == 0} | ||||
|             bind:value={address.address1} | ||||
|             type="text" | ||||
|             name="address1" | ||||
|             class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" | ||||
|           /> | ||||
|         </div> | ||||
|         <div class="col-span-6"> | ||||
|           <label for="address2" class="block text-sm font-medium text-gray-700" | ||||
|             >{$_("apartment-suite-etc")}</label | ||||
|           > | ||||
|           <input | ||||
|             autocomplete="off" | ||||
|             placeholder={$_("apartment-suite-etc")} | ||||
|             bind:value={address.address2} | ||||
|             type="text" | ||||
|             name="address2" | ||||
|             class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" | ||||
|           /> | ||||
|         </div> | ||||
|         <div class="col-span-6"> | ||||
|           <label for="zipcode" class="block text-sm font-medium text-gray-700" | ||||
|             >{$_("zip-postal-code")}</label | ||||
|           > | ||||
|           <input | ||||
|             autocomplete="off" | ||||
|             placeholder={$_("zip-postal-code")} | ||||
|             class:border-red-500={address.postalcode.length == 0} | ||||
|             class:focus:border-red-500={address.postalcode.length == 0} | ||||
|             class:focus:ring-red-500={address.postalcode.length == 0} | ||||
|             bind:value={address.postalcode} | ||||
|             type="text" | ||||
|             name="zipcode" | ||||
|             class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" | ||||
|           /> | ||||
|         </div> | ||||
|         <div class="col-span-6"> | ||||
|           <label for="city" class="block text-sm font-medium text-gray-700" | ||||
|             >City</label | ||||
|           > | ||||
|           <input | ||||
|             autocomplete="off" | ||||
|             placeholder="City" | ||||
|             class:border-red-500={address.city.length == 0} | ||||
|             class:focus:border-red-500={address.city.length == 0} | ||||
|             class:focus:ring-red-500={address.city.length == 0} | ||||
|             bind:value={address.city} | ||||
|             type="text" | ||||
|             name="city" | ||||
|             class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2" | ||||
|           /> | ||||
|         </div> | ||||
|       {/if} | ||||
|     </div> | ||||
|     <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} | ||||
|           type="number" | ||||
|           step="0.01" | ||||
|           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="2.00" | ||||
|         /> | ||||
|         <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> | ||||
|       <h4 class="text-xl font-semibold"> | ||||
|         {$_("confirm")} | ||||
|       </h4> | ||||
|       <button | ||||
|         disabled={amount <= 0 || | ||||
|           runnerinfo.id == 0 || | ||||
|           (donorinfo.firstname.length == 0 || donorinfo.lastname.length == 0)} | ||||
|         class="py-2 px-4 text-center inline-flex items-center text-md font-medium rounded-lg border border-transparent bg-blue-100 text-blue-800 hover:bg-blue-200 focus:outline-hidden focus:bg-blue-200 disabled:opacity-50 disabled:pointer-events-none dark:text-blue-500 dark:bg-blue-800/30 dark:hover:bg-blue-800/20 dark:focus:bg-blue-800/20" | ||||
|         on:click={async () => { | ||||
|           toast.loading($_("creating-donation")); | ||||
|           if (donorinfo.id == 0) { | ||||
|             if (!address_checked) { | ||||
|               address = null | ||||
|             } | ||||
|             donorinfo = await DonorService.donorControllerPost({ | ||||
|               firstname: donorinfo.firstname, | ||||
|               lastname: lastname, | ||||
|               receiptNeeded: address_checked, | ||||
|               address: address, | ||||
|             }); | ||||
|             loadDonors(); | ||||
|           } | ||||
|           await DonationService.donationControllerPostDistance({ | ||||
|             amountPerDistance: amount*100, | ||||
|             runner: runnerinfo.id, | ||||
|             donor: donorinfo.id, | ||||
|           }); | ||||
|           toast.dismiss(); | ||||
|           toast.success($_("donation-created")); | ||||
|           resetAll(); | ||||
|         }}>{$_("create")}</button | ||||
|       > | ||||
|       {amount <= 0 || | ||||
|           runnerinfo.id == 0 || | ||||
|           (donorinfo.firstname.length == 0 && donorinfo.lastname.length == 0)} | ||||
|           {amount} - {runnerinfo.id} - {donorinfo.id} - {donorinfo.firstname} - {donorinfo.lastname} | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <style> | ||||
|   :global(:root) { | ||||
|     --sv-bg: #ffffff; | ||||
|   } | ||||
| </style> | ||||
		Reference in New Issue
	
	Block a user