205
									
								
								src/components/statsclients/AddScanStationModal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								src/components/statsclients/AddScanStationModal.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,205 @@ | ||||
| <script> | ||||
|   import { _ } from "svelte-i18n"; | ||||
|   import { clickOutside } from "../base/outsideclick"; | ||||
|   import { focusTrap } from "svelte-focus-trap"; | ||||
|   import { ScanStationService, TrackService } from "@odit/lfk-client-js"; | ||||
|   import Toastify from "toastify-js"; | ||||
|   import Select from "svelte-select"; | ||||
|   export let modal_open; | ||||
|   export let new_station; | ||||
|   export let current_stations; | ||||
|   export let copy_modal_open; | ||||
|   let tracks = []; | ||||
|   TrackService.trackControllerGetAll().then((val) => { | ||||
|     tracks = val.map((t) => { | ||||
|       return { label: t.name || `#${t.id}`, value: t }; | ||||
|     }); | ||||
|   }); | ||||
|   function focus(el) { | ||||
|     el.focus(); | ||||
|   } | ||||
|   $: description = ""; | ||||
|   $: track = null; | ||||
|   $: enabled = true; | ||||
|   $: createbtnenabled = track != null; | ||||
|   $: processed_last_submit = true; | ||||
|   (() => { | ||||
|     document.onkeydown = (e) => { | ||||
|       e = e || window.event; | ||||
|       if (e.key === "Escape") { | ||||
|         modal_open = false; | ||||
|       } | ||||
|       if (e.keyCode === 13) { | ||||
|         if (createbtnenabled === true) { | ||||
|           createbtnenabled = false; | ||||
|           submit(); | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|   })(); | ||||
|   function submit() { | ||||
|     if (processed_last_submit === true) { | ||||
|       processed_last_submit = false; | ||||
|       const toast = Toastify({ | ||||
|         text: $_("scanstation-is-being-added"), | ||||
|         duration: -1, | ||||
|       }).showToast(); | ||||
|       let postdata = { | ||||
|         description, | ||||
|         enabled, | ||||
|         track, | ||||
|       }; | ||||
|       ScanStationService.scanStationControllerPost(postdata) | ||||
|         .then((result) => { | ||||
|           description = ""; | ||||
|           track = tracks[0].id; | ||||
|           enabled = true; | ||||
|           modal_open = false; | ||||
|           // | ||||
|           Toastify({ | ||||
|             text: $_("scanstation-added"), | ||||
|             duration: 500, | ||||
|             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||
|           }).showToast(); | ||||
|           current_stations.push(result); | ||||
|           current_stations = current_stations; | ||||
|           new_station = result; | ||||
|           copy_modal_open = true; | ||||
|         }) | ||||
|         .catch((err) => { | ||||
|           // | ||||
|         }) | ||||
|         .finally(() => { | ||||
|           processed_last_submit = true; | ||||
|           // | ||||
|           toast.hideToast(); | ||||
|         }); | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| {#if modal_open} | ||||
|   <div | ||||
|     class="fixed z-10 inset-0 overflow-y-auto" | ||||
|     use:focusTrap | ||||
|     use:clickOutside | ||||
|     on:click_outside={() => { | ||||
|       modal_open = false; | ||||
|     }}> | ||||
|     <div | ||||
|       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||
|       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||
|         <div | ||||
|           class="absolute inset-0 bg-gray-500 opacity-75" | ||||
|           data-id="modal_backdrop" /> | ||||
|       </div> | ||||
|       <span | ||||
|         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||
|         aria-hidden="true">​</span> | ||||
|       <div | ||||
|         class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||
|         role="dialog" | ||||
|         aria-modal="true" | ||||
|         aria-labelledby="modal-headline"> | ||||
|         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||
|           <div class="sm:flex sm:items-start"> | ||||
|             <div | ||||
|               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||
|               <svg | ||||
|                 class="h-6 w-6 text-blue-600" | ||||
|                 fill="currentColor" | ||||
|                 xmlns="http://www.w3.org/2000/svg" | ||||
|                 viewBox="0 0 24 24" | ||||
|                 width="24" | ||||
|                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||
|                 <path | ||||
|                   d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> | ||||
|             </div> | ||||
|             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||
|               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||
|                 {$_('create-a-new-scanstation')} | ||||
|               </h3> | ||||
|               <div class="mt-2 mb-6"> | ||||
|                 <p class="text-sm text-gray-500"> | ||||
|                   {$_('please-provide-the-required-information-to-create-a-new-scanstation')} | ||||
|                 </p> | ||||
|               </div> | ||||
|               <div class="grid grid-cols-6 gap-6"> | ||||
|                 <div class="col-span-6"> | ||||
|                   <label | ||||
|                     for="track" | ||||
|                     class="block text-sm font-medium text-gray-700">Track</label> | ||||
|                   <Select | ||||
|                     containerClasses="rounded-l-md 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-gray-500 rounded-md p-2" | ||||
|                     itemFilter={(label, filterText, option) => label | ||||
|                         .toLowerCase() | ||||
|                         .includes( | ||||
|                           filterText.toLowerCase() | ||||
|                         ) || option.value.id | ||||
|                         .toString() | ||||
|                         .startsWith(filterText.toLowerCase())} | ||||
|                     items={tracks} | ||||
|                     showChevron={true} | ||||
|                     placeholder="Search for a track (by name or id)." | ||||
|                     noOptionsMessage="No track found" | ||||
|                     on:select={(selectedValue) => (track = selectedValue.detail.value.id)} | ||||
|                     on:clear={() => (track = null)} /> | ||||
|                 </div> | ||||
|                 <div class="col-span-6"> | ||||
|                   <label | ||||
|                     for="description" | ||||
|                     class="block text-sm font-medium text-gray-700">{$_('description')}</label> | ||||
|                   <input | ||||
|                     use:focus | ||||
|                     autocomplete="off" | ||||
|                     placeholder={$_('description')} | ||||
|                     bind:value={description} | ||||
|                     type="text" | ||||
|                     name="description" | ||||
|                     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-gray-500 rounded-md p-2" /> | ||||
|                 </div> | ||||
|                 <div class="col-span-6"> | ||||
|                   <label | ||||
|                     for="enabled" | ||||
|                     class="font-medium text-gray-700">{$_('enabled_large')}</label> | ||||
|                   <br /> | ||||
|                   <p class="text-gray-500"> | ||||
|                     <input | ||||
|                       id="enabled" | ||||
|                       on:change={() => { | ||||
|                         enabled = !enabled; | ||||
|                       }} | ||||
|                       name="enabled" | ||||
|                       type="checkbox" | ||||
|                       checked={enabled} | ||||
|                       class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||
|                     {$_('this-scanstation-is')} | ||||
|                     {#if enabled}{$_('enabled')}{:else}{$_('disabled')}{/if} | ||||
|                   </p> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||
|           <button | ||||
|             disabled={!createbtnenabled} | ||||
|             class:opacity-50={!createbtnenabled} | ||||
|             on:click={submit} | ||||
|             type="button" | ||||
|             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||
|             {$_('create')} | ||||
|           </button> | ||||
|           <button | ||||
|             on:click={() => { | ||||
|               modal_open = false; | ||||
|             }} | ||||
|             type="button" | ||||
|             class="mt-3 w-full inline-flex 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 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||
|             {$_('cancel')} | ||||
|           </button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| {/if} | ||||
| @@ -0,0 +1,92 @@ | ||||
| <script> | ||||
|   import { _ } from "svelte-i18n"; | ||||
|   import { clickOutside } from "../base/outsideclick"; | ||||
|   import { focusTrap } from "svelte-focus-trap"; | ||||
|   import { ScanStationService } from "@odit/lfk-client-js"; | ||||
|   import Toastify from "toastify-js"; | ||||
|   import { createEventDispatcher } from "svelte"; | ||||
|   export let modal_open; | ||||
|   export let delete_station; | ||||
|   const dispatch = createEventDispatcher(); | ||||
|   function cancelDelete() { | ||||
|     modal_open = false; | ||||
|     dispatch("cancelDelete", { id: delete_station.id }); | ||||
|   } | ||||
|   function deleteStation() { | ||||
|     ScanStationService.donorControllerRemove( | ||||
|       delete_station.id, | ||||
|       true | ||||
|     ) | ||||
|       .then((resp) => { | ||||
|         Toastify({ | ||||
|           text: $_('station-deleted'), | ||||
|           duration: 500, | ||||
|           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||
|         }).showToast(); | ||||
|         location.replace("./"); | ||||
|       }) | ||||
|       .catch((err) => {}); | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| {#if modal_open} | ||||
|   <div | ||||
|     class="fixed z-10 inset-0 overflow-y-auto" | ||||
|     use:focusTrap | ||||
|     use:clickOutside | ||||
|     on:click_outside={cancelDelete}> | ||||
|     <div | ||||
|       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||
|       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||
|         <div | ||||
|           class="absolute inset-0 bg-gray-500 opacity-75" | ||||
|           data-id="modal_backdrop" /> | ||||
|       </div> | ||||
|       <span | ||||
|         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||
|         aria-hidden="true">​</span> | ||||
|       <div | ||||
|         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||
|         role="dialog" | ||||
|         aria-modal="true" | ||||
|         aria-labelledby="modal-headline"> | ||||
|         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||
|           <div class="sm:flex sm:items-start"> | ||||
|             <div | ||||
|               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||
|               <svg class="h-6 w-6 text-blue-600" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"/></svg> | ||||
|             </div> | ||||
|             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||
|               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||
|                 {$_('attention')} | ||||
|               </h3> | ||||
|               <div class="mt-2 mb-6"> | ||||
|                 <p class="text-sm text-gray-500"> | ||||
|                   {$_( | ||||
|                     'do-you-want-to-delete-this-donor-with-all-related-donations' | ||||
|                   )} | ||||
|                   <br />  | ||||
|                   {$_('all-associated-scans-will-get-deleted-as-well')} | ||||
|                 </p> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||
|           <button | ||||
|             on:click={deleteStation} | ||||
|             type="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 sm:ml-3 sm:w-auto sm:text-sm"> | ||||
|             {$_('confirm-delete-station-with-all-scans')} | ||||
|           </button> | ||||
|           <button | ||||
|             on:click={cancelDelete} | ||||
|             type="button" | ||||
|             class="mt-3 w-full inline-flex 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 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | ||||
|             {$_('cancel-keep-station')} | ||||
|           </button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| {/if} | ||||
							
								
								
									
										129
									
								
								src/components/statsclients/CopyScanStationTokenModal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/components/statsclients/CopyScanStationTokenModal.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| <script> | ||||
|   import { _ } from "svelte-i18n"; | ||||
|   import { focusTrap } from "svelte-focus-trap"; | ||||
|   import Toastify from "toastify-js"; | ||||
|   import { tick, createEventDispatcher } from "svelte"; | ||||
|   export let copy_modal_open; | ||||
|   export let new_station; | ||||
|   const dispatch = createEventDispatcher(); | ||||
|   let valueCopy = null; | ||||
|   let areaDom; | ||||
|   let copied = false; | ||||
|   function close() { | ||||
|     copy_modal_open = false; | ||||
|   } | ||||
|   async function copy() { | ||||
|     valueCopy = new_station.key; | ||||
|     await tick(); | ||||
|     areaDom.focus(); | ||||
|     areaDom.select(); | ||||
|     try { | ||||
|       const successful = document.execCommand("copy"); | ||||
|       if (!successful) { | ||||
|         throw new Error(); | ||||
|       } | ||||
|       Toastify({ | ||||
|         text: $_("copied-token-to-clipboard"), | ||||
|         duration: 500, | ||||
|         backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||
|       }).showToast(); | ||||
|       copied = true; | ||||
|     } catch (err) { | ||||
|       Toastify({ | ||||
|         text: $_("error-whyile-copying-to-clipboard"), | ||||
|         duration: 500, | ||||
|         backgroundColor: | ||||
|           "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", | ||||
|       }).showToast(); | ||||
|     } | ||||
|  | ||||
|     // we can notifi by event or storage about copy status | ||||
|     valueCopy = null; | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| {#if copy_modal_open} | ||||
|   {#if valueCopy != null} | ||||
|     <textarea bind:this={areaDom}>{valueCopy}</textarea> | ||||
|   {/if} | ||||
|   <div class="fixed z-10 inset-0 overflow-y-auto" use:focusTrap> | ||||
|     <div | ||||
|       class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | ||||
|       <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | ||||
|         <div | ||||
|           class="absolute inset-0 bg-gray-500 opacity-75" | ||||
|           data-id="modal_backdrop" /> | ||||
|       </div> | ||||
|       <span | ||||
|         class="hidden sm:inline-block sm:align-middle sm:h-screen" | ||||
|         aria-hidden="true">​</span> | ||||
|       <div | ||||
|         class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" | ||||
|         role="dialog" | ||||
|         aria-modal="true" | ||||
|         aria-labelledby="modal-headline"> | ||||
|         <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | ||||
|           <div class="sm:flex sm:items-start"> | ||||
|             <div | ||||
|               class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"> | ||||
|               <svg | ||||
|                 class="h-6 w-6 text-blue-600" | ||||
|                 fill="currentColor" | ||||
|                 xmlns="http://www.w3.org/2000/svg" | ||||
|                 viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> | ||||
|                 <path | ||||
|                   d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> | ||||
|             </div> | ||||
|             <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | ||||
|               <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||
|                 {$_('token')} | ||||
|               </h3> | ||||
|               <div class="mt-2 mb-6"> | ||||
|                 <p class="text-sm text-gray-500"> | ||||
|                   {$_('the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again')} | ||||
|                   <br /> | ||||
|                   {$_('please-copy-the-token-and-store-it-somewhere-save')} | ||||
|                 </p> | ||||
|               </div> | ||||
|               <div class="mt-2 mb-6"> | ||||
|                 <label | ||||
|                   for="token" | ||||
|                   class="block text-sm font-medium text-gray-700">{$_('token')}</label> | ||||
|                 <div on:click={copy} class="inline-flex"> | ||||
|                   <p | ||||
|                     name="token" | ||||
|                     class:bg-green-200={copied} | ||||
|                     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-gray-500 p-2"> | ||||
|                     {new_station.key} | ||||
|                   </p> | ||||
|                   <div | ||||
|                     class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer"> | ||||
|                     <svg | ||||
|                       xmlns="http://www.w3.org/2000/svg" | ||||
|                       viewBox="0 0 24 24" | ||||
|                       width="24" | ||||
|                       height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||
|                       <path | ||||
|                         fill="currentColor" | ||||
|                         d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <p class="text-gray-500 text-xs"> | ||||
|                   {$_('click-to-copy-token-to-clipboard')} | ||||
|                 </p> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | ||||
|           <button | ||||
|             on:click={close} | ||||
|             type="button" | ||||
|             class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||
|             {$_('yes-i-copied-the-token')} | ||||
|           </button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| {/if} | ||||
							
								
								
									
										206
									
								
								src/components/statsclients/ScanStationDetail.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								src/components/statsclients/ScanStationDetail.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | ||||
| <script> | ||||
|   import { t, _ } from "svelte-i18n"; | ||||
|   import store from "../../store"; | ||||
|   import { ScanStationService, TrackService } from "@odit/lfk-client-js"; | ||||
|   import Toastify from "toastify-js"; | ||||
|   import PromiseError from "../base/PromiseError.svelte"; | ||||
|   import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte"; | ||||
|   import Select from "svelte-select"; | ||||
|   let data_loaded = false; | ||||
|   let modal_open; | ||||
|   let delete_station; | ||||
|   export let params; | ||||
|   $: delete_triggered = false; | ||||
|   $: original_data = {}; | ||||
|   $: editable = {}; | ||||
|   $: tracks = []; | ||||
|   $: track = {}; | ||||
|   $: changes_performed = !( | ||||
|     JSON.stringify(original_data) === JSON.stringify(editable) | ||||
|   ); | ||||
|   $: save_enabled = changes_performed; | ||||
|   const promise = ScanStationService.scanStationControllerGetOne( | ||||
|     params.stationid | ||||
|   ).then((data) => { | ||||
|     data_loaded = true; | ||||
|     data.track = data.track.id; | ||||
|     original_data = Object.assign(original_data, data); | ||||
|     editable = Object.assign(editable, original_data); | ||||
|     TrackService.trackControllerGetAll().then((val) => { | ||||
|       tracks = val.map((t) => { | ||||
|         return { label: t.name || `#{t.id}`, value: t }; | ||||
|       }); | ||||
|       track = tracks.find((t) => t.value.id == editable.track); | ||||
|     }); | ||||
|   }); | ||||
|   function submit() { | ||||
|     if (data_loaded === true && save_enabled) { | ||||
|       Toastify({ | ||||
|         text: $_("station-is-being-updated"), | ||||
|         duration: 2500, | ||||
|       }).showToast(); | ||||
|       ScanStationService.scanStationControllerPut(original_data.id, editable) | ||||
|         .then((resp) => { | ||||
|           Object.assign(original_data, editable); | ||||
|           original_data = original_data; | ||||
|           Toastify({ | ||||
|             text: $_("updated-station"), | ||||
|             duration: 2500, | ||||
|             backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||
|           }).showToast(); | ||||
|         }) | ||||
|         .catch((err) => {}); | ||||
|     } else { | ||||
|     } | ||||
|   } | ||||
|   function deleteStation() { | ||||
|     ScanStationService.scanStationControllerRemove(original_data.id, false) | ||||
|       .then((resp) => { | ||||
|         Toastify({ | ||||
|           text: $_("station-deleted"), | ||||
|           duration: 500, | ||||
|           backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", | ||||
|         }).showToast(); | ||||
|         location.replace("./"); | ||||
|       }) | ||||
|       .catch((err) => { | ||||
|         modal_open = true; | ||||
|         delete_station = original_data; | ||||
|       }); | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <ConfirmScanStationDeletion bind:modal_open bind:delete_station /> | ||||
| {#await promise} | ||||
|   {$_('loading-station-details')} | ||||
| {:then} | ||||
|   <section class="container p-5 select-none"> | ||||
|     <div class="flex flex-row mb-4"> | ||||
|       <div class="w-full"> | ||||
|         <nav class="w-full flex"> | ||||
|           <ol class="list-none flex flex-row items-center justify-start"> | ||||
|             <li class="flex items-center"> | ||||
|               <svg | ||||
|                 fill="currentColor" | ||||
|                 xmlns="http://www.w3.org/2000/svg" | ||||
|                 viewBox="0 0 24 24" | ||||
|                 width="24" | ||||
|                 height="24"><path fill="none" d="M0 0h24v24H0z" /> | ||||
|                 <path | ||||
|                   d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> | ||||
|             </li> | ||||
|             <li class="flex items-center ml-2"> | ||||
|               <a class="mr-2" href="./">{$_('scanstation')}</a><svg | ||||
|                 stroke="currentColor" | ||||
|                 fill="none" | ||||
|                 stroke-width="2" | ||||
|                 viewBox="0 0 24 24" | ||||
|                 stroke-linecap="round" | ||||
|                 stroke-linejoin="round" | ||||
|                 class="h-3 w-3 mr-2 stroke-current" | ||||
|                 height="1em" | ||||
|                 width="1em" | ||||
|                 xmlns="http://www.w3.org/2000/svg"><line | ||||
|                   x1="5" | ||||
|                   y1="12" | ||||
|                   x2="19" | ||||
|                   y2="12" /> | ||||
|                 <polyline points="12 5 19 12 12 19" /></svg> | ||||
|             </li> | ||||
|             <li class="flex items-center"> | ||||
|               <span class="mr-2">#{original_data.id}</span> | ||||
|             </li> | ||||
|           </ol> | ||||
|         </nav> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="mb-8 text-3xl font-extrabold leading-tight"> | ||||
|       #{original_data.id} | ||||
|       <span data-id="stations_actions_${editable.id}"> | ||||
|         {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:DELETE')} | ||||
|           {#if delete_triggered} | ||||
|             <button | ||||
|               on:click={deleteStation} | ||||
|               class="w-full 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 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button> | ||||
|             <button | ||||
|               on:click={() => { | ||||
|                 delete_triggered = !delete_triggered; | ||||
|               }} | ||||
|               class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> | ||||
|           {/if} | ||||
|           {#if !delete_triggered} | ||||
|             <button | ||||
|               on:click={() => { | ||||
|                 delete_triggered = true; | ||||
|               }} | ||||
|               type="button" | ||||
|               class="w-full 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 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-station')}</button> | ||||
|           {/if} | ||||
|         {/if} | ||||
|         {#if !delete_triggered} | ||||
|           <button | ||||
|             disabled={!save_enabled} | ||||
|             class:opacity-50={!save_enabled} | ||||
|             type="button" | ||||
|             on:click={submit} | ||||
|             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:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> | ||||
|         {/if} | ||||
|       </span> | ||||
|     </div> | ||||
|     <!--  --> | ||||
|     <div class="text-sm w-full"> | ||||
|       <label | ||||
|         for="track" | ||||
|         class="block text-sm font-medium text-gray-700">Track</label> | ||||
|       <Select | ||||
|         containerClasses="rounded-l-md 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-gray-500 rounded-md p-2" | ||||
|         itemFilter={(label, filterText, option) => label | ||||
|             .toLowerCase() | ||||
|             .includes( | ||||
|               filterText.toLowerCase() | ||||
|             ) || option.value.id | ||||
|             .toString() | ||||
|             .startsWith(filterText.toLowerCase())} | ||||
|         items={tracks} | ||||
|         showChevron={true} | ||||
|         placeholder="Search for a track (by name or id)." | ||||
|         noOptionsMessage="No track found" | ||||
|         bind:selectedValue={track} | ||||
|         on:select={(selectedValue) => (editable.track = selectedValue.detail.value.id)} | ||||
|         on:clear={() => (track = null)} /> | ||||
|     </div> | ||||
|     <div class="text-sm w-full"> | ||||
|       <label | ||||
|         for="description" | ||||
|         class="font-medium text-gray-700">{$_('description')}</label> | ||||
|       <input | ||||
|         autocomplete="off" | ||||
|         placeholder={$_('description')} | ||||
|         type="text" | ||||
|         bind:value={editable.description} | ||||
|         name="description" | ||||
|         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-gray-500 rounded-md p-2" /> | ||||
|     </div> | ||||
|     <div class="text-sm w-full"> | ||||
|       <label | ||||
|         for="enabled" | ||||
|         class="ml-1 font-medium text-gray-700">{$_('enabled')}</label> | ||||
|       <br /> | ||||
|       <p class="text-gray-500"> | ||||
|         <input | ||||
|           id="enabled" | ||||
|           on:change={() => { | ||||
|             editable.enabled = !editable.enabled; | ||||
|           }} | ||||
|           name="enabled" | ||||
|           type="checkbox" | ||||
|           checked={editable.enabled} | ||||
|           class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> | ||||
|         {$_('this-scanstation-is')} | ||||
|         {#if editable.enabled}{$_('enabled')}{:else}{$_('disabled')}{/if} | ||||
|       </p> | ||||
|     </div> | ||||
|   </section> | ||||
| {:catch error} | ||||
|   <PromiseError {error} /> | ||||
| {/await} | ||||
							
								
								
									
										33
									
								
								src/components/statsclients/StatsClients.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/components/statsclients/StatsClients.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| <script> | ||||
|   import { _ } from "svelte-i18n"; | ||||
|   import store from "../../store"; | ||||
|   import AddStatsClientsModal from "./AddStatsClientsModal.svelte"; | ||||
| import CopyStatsClientsTokenModal from "./CopyStatsClientsTokenModal.svelte"; | ||||
|   import StatsClientssOverview from "./StatsClientsOverview.svelte"; | ||||
|   export let modal_open = false; | ||||
|   export let copy_modal_open = false; | ||||
|   export let new_station = {}; | ||||
|   let current_stations = []; | ||||
| </script> | ||||
|  | ||||
| <section class="container p-5"> | ||||
|   <span class="mb-1 text-3xl font-extrabold leading-tight"> | ||||
|     {$_('statsclients')} | ||||
|     {#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:CREATE')} | ||||
|       <button | ||||
|         on:click={() => { | ||||
|           modal_open = true; | ||||
|         }} | ||||
|         type="button" | ||||
|         class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | ||||
|         {$_('create-a-new-statsclient')} | ||||
|       </button> | ||||
|     {/if} | ||||
|   </span> | ||||
|   <StatsClientssOverview bind:current_stations bind:modal_open bind:new_station bind:copy_modal_open /> | ||||
| </section> | ||||
|  | ||||
| {#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:CREATE')} | ||||
| <AddStatsClientsModal bind:modal_open bind:current_stations bind:new_station bind:copy_modal_open/> | ||||
| <CopyStatsClientsTokenModal bind:copy_modal_open bind:new_station /> | ||||
| {/if} | ||||
							
								
								
									
										21
									
								
								src/components/statsclients/StatsClientsEmptyState.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/components/statsclients/StatsClientsEmptyState.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| <script> | ||||
|   import { _ } from "svelte-i18n"; | ||||
| import AddScanStationModal from "./AddScanStationModal.svelte"; | ||||
| import CopyScanStationTokenModal from "./CopyScanStationTokenModal.svelte"; | ||||
|   import scanstations_empty from "./scanstations_empty.svg"; | ||||
|   let modal_open = false; | ||||
|   let copy_modal_open = false; | ||||
|   let new_station = {}; | ||||
|   let current_stations = []; | ||||
| </script> | ||||
|  | ||||
| <div class="text-center items-center justify-center"> | ||||
|   <p class="mb-16 text-lg text-gray-500"> | ||||
|     <img class="w-full h-44" src={scanstations_empty} alt="" /> | ||||
|     <span class="font-bold">{$_('you-dont-have-any-scanstations-yet')}.</span><br /> | ||||
|     <span>{$_('add-the-first-scanstation')}</span> | ||||
|   </p> | ||||
| </div> | ||||
|  | ||||
| <AddScanStationModal bind:modal_open bind:current_stations bind:new_station bind:copy_modal_open/> | ||||
| <CopyScanStationTokenModal bind:copy_modal_open bind:new_station /> | ||||
							
								
								
									
										163
									
								
								src/components/statsclients/StatsClientsOverview.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								src/components/statsclients/StatsClientsOverview.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | ||||
| <script> | ||||
|   import { _ } from "svelte-i18n"; | ||||
|   import Toastify from "toastify-js"; | ||||
|   import { StatsClientService } from "@odit/lfk-client-js"; | ||||
|   const promise = StatsClientService.statsClientControllerGetAll().then( | ||||
|     (result) => { | ||||
|       current_clients = result; | ||||
|     } | ||||
|   ); | ||||
|   import store from "../../store"; | ||||
|   import StatsClientsEmptyState from "./StatsClientsEmptyState.svelte"; | ||||
|   import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte"; | ||||
|   $: searchvalue = ""; | ||||
|   $: active_deletes = []; | ||||
|   let delete_client = {}; | ||||
|   let modal_open = false; | ||||
|   export let current_clients = []; | ||||
| </script> | ||||
|  | ||||
| <ConfirmScanStationDeletion | ||||
|   on:cancelDelete={(event) => { | ||||
|     modal_open = false; | ||||
|     active_deletes[event.detail.id] = false; | ||||
|   }} | ||||
|   bind:modal_open | ||||
|   bind:delete_client /> | ||||
| {#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:GET')} | ||||
|   {#await promise} | ||||
|     <div | ||||
|       class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||
|       role="alert"> | ||||
|       <p class="font-bold">{$_('statsclients-are-being-loaded')}</p> | ||||
|       <p class="text-sm">{$_('this-might-take-a-moment')}</p> | ||||
|     </div> | ||||
|   {:then} | ||||
|     {#if current_clients.length === 0} | ||||
|       <StatsClientsEmptyState /> | ||||
|     {:else} | ||||
|       <input | ||||
|         type="search" | ||||
|         bind:value={searchvalue} | ||||
|         placeholder={$_('datatable.search')} | ||||
|         aria-label={$_('datatable.search')} | ||||
|         class="gridjs-input gridjs-search-input mb-4" /> | ||||
|       <div | ||||
|         class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> | ||||
|         <table class="divide-y divide-gray-200 w-full"> | ||||
|           <thead class="bg-gray-50"> | ||||
|             <tr> | ||||
|               <th | ||||
|               scope="col" | ||||
|               class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||
|               {$_('description')} | ||||
|             </th> | ||||
|             <th | ||||
|               scope="col" | ||||
|               class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||
|               {$_('prefix')} | ||||
|             </th> | ||||
|               <th | ||||
|                 scope="col" | ||||
|                 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||||
|                 {$_('key')} | ||||
|               </th> | ||||
|               <th scope="col" class="relative px-6 py-3"> | ||||
|                 <span class="sr-only">{$_('action')}</span> | ||||
|               </th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody class="divide-y divide-gray-200"> | ||||
|             {#each current_clients as c} | ||||
|               {#if Object.values(c) | ||||
|                 .toString() | ||||
|                 .toLowerCase() | ||||
|                 .includes(searchvalue)} | ||||
|                 <tr data-rowid="station_{c.id}"> | ||||
|                   <td class="px-6 py-4 whitespace-nowrap"> | ||||
|                     <div class="flex items-center"> | ||||
|                       <div class="ml-4"> | ||||
|                         <div class="text-sm font-medium text-gray-900"> | ||||
|                             {c.description} | ||||
|                         </div> | ||||
|                       </div> | ||||
|                     </div> | ||||
|                   </td> | ||||
|                   <td class="px-6 py-4 whitespace-nowrap"> | ||||
|                     <div class="flex items-center"> | ||||
|                       <div class="ml-4"> | ||||
|                         <div class="text-sm font-medium text-gray-900"> | ||||
|                           {c.prefix} | ||||
|                         </div> | ||||
|                       </div> | ||||
|                     </div> | ||||
|                   </td> | ||||
|                   <td class="px-6 py-4 whitespace-nowrap"> | ||||
|                     <div class="flex items-center"> | ||||
|                       <div class="ml-4"> | ||||
|                         <div class="text-sm font-medium text-gray-900"> | ||||
|                           {c.key} | ||||
|                         </div> | ||||
|                       </div> | ||||
|                   </td> | ||||
|                   {#if active_deletes[c.id] === true} | ||||
|                     <td | ||||
|                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||
|                       <button | ||||
|                         on:click={() => { | ||||
|                           active_deletes[c.id] = false; | ||||
|                         }} | ||||
|                         tabindex="0" | ||||
|                         class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> | ||||
|                       <button | ||||
|                         on:click={() => { | ||||
|                           StatsClientService. (s.id, false) | ||||
|                             .then((resp) => { | ||||
|                               current_clients = current_clients.filter((obj) => obj.id !== s.id); | ||||
|                               Toastify({ | ||||
|                                 text: $_('statsclient-deleted'), | ||||
|                                 duration: 500, | ||||
|                                 backgroundColor: | ||||
|                                   'linear-gradient(to right, #00b09b, #96c93d)', | ||||
|                               }).showToast(); | ||||
|                             }) | ||||
|                             .catch((err) => { | ||||
|                               modal_open = true; | ||||
|                               delete_client = c; | ||||
|                             }); | ||||
|                         }} | ||||
|                         tabindex="0" | ||||
|                         class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> | ||||
|                     </td> | ||||
|                   {:else} | ||||
|                     <td | ||||
|                       class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | ||||
|                       <a | ||||
|                         href="/statsclients/{c.id}" | ||||
|                         class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> | ||||
|                       {#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:DELETE')} | ||||
|                         <button | ||||
|                           on:click={() => { | ||||
|                             active_deletes[c.id] = true; | ||||
|                           }} | ||||
|                           tabindex="0" | ||||
|                           class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> | ||||
|                       {/if} | ||||
|                     </td> | ||||
|                   {/if} | ||||
|                 </tr> | ||||
|               {/if} | ||||
|             {/each} | ||||
|           </tbody> | ||||
|         </table> | ||||
|       </div> | ||||
|     {/if} | ||||
|   {:catch error} | ||||
|     <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> | ||||
|       <span class="inline-block align-middle mr-8"> | ||||
|         <b class="capitalize">{$_('general_promise_error')}</b> | ||||
|         {error} | ||||
|       </span> | ||||
|     </div> | ||||
|   {/await} | ||||
| {/if} | ||||
							
								
								
									
										1
									
								
								src/components/statsclients/scanstations_empty.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/components/statsclients/scanstations_empty.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 5.0 KiB | 
		Reference in New Issue
	
	Block a user