ScansOverview: migrate to datatable
All checks were successful
continuous-integration/drone/push Build is passing

close #168
This commit is contained in:
Philipp Dormann 2023-03-19 12:28:27 +01:00
parent 2dc8ffba32
commit 754931b2f6
2 changed files with 171 additions and 92 deletions

View File

@ -1,125 +1,140 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
ScanService,
} from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import { DataHandler, Datatable, Th, ThFilter } from "@vincjo/datatables";
import { ScanService } from "@odit/lfk-client-js";
import store from "../../store";
import Toastify from "toastify-js";
import ScansEmptyState from "./ScansEmptyState.svelte";
$: searchvalue = "";
import ThFilterRunner from "./ThFilterRunner.svelte";
$: active_deletes = [];
export let current_scans = [];
const handler = new DataHandler(current_scans, { rowsPerPage: 20 });
const rows = handler.getRows();
const scans_promise = ScanService.scanControllerGetAll().then((val) => {
current_scans = val;
handler.setRows(val);
});
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
function format_laptime(laptime) {
if (laptime == 0 || laptime == null) {
return $_("first-scan-of-the-day");
}
return id.toString() === searchvalue;
}
function format_laptime(laptime){
if(laptime == 0 || laptime == null){return $_('first-scan-of-the-day')}
if(laptime < 60){return `${laptime}s`}
if(laptime < 3600){return `${Math.floor(laptime / 60)}min ${laptime - (Math.floor(laptime / 60)*60)}s`}
return `${Math.floor(laptime / 3600)}h ${laptime - (Math.floor(laptime / 3600)*3600)}min ${laptime - (Math.floor(laptime / 3600)*3600) - (Math.floor(laptime / 60)*60)}`
if (laptime < 60) {
return `${laptime}s`;
}
if (laptime < 3600) {
return `${Math.floor(laptime / 60)}min ${
laptime - Math.floor(laptime / 60) * 60
}s`;
}
return `${Math.floor(laptime / 3600)}h ${
laptime - Math.floor(laptime / 3600) * 3600
}min ${
laptime -
Math.floor(laptime / 3600) * 3600 -
Math.floor(laptime / 60) * 60
}`;
}
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')}
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")}
{#await scans_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">{$_('scans-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
role="alert"
>
<p class="font-bold">{$_("scans-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then}
{#if current_scans.length === 0}
<ScansEmptyState />
{: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">
{$_('runner')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('distance-track')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('laptime')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('status')}
</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_scans as scan}
{#if scan.track?.name
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || scan.runner?.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || scan.runner?.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(scan.id)}
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<Datatable {handler}>
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<Th {handler} orderBy="id">ID</Th>
<Th {handler}>
{$_("runner")}
</Th>
<Th {handler}>
{$_("distance")}
</Th>
<Th {handler}>
{$_("track")}
</Th>
<Th {handler}>
{$_("laptime")}
</Th>
<Th {handler}>
{$_("status")}
</Th>
<th
scope="col"
class="relative px-6 py-3"
style="border-bottom: 1px solid #ddd;"
>
{$_("action")}
</th>
</tr>
<tr>
<ThFilter {handler} filterBy="id" />
<ThFilterRunner {handler} />
<th style="border-bottom: 1px solid #ddd;" />
<th style="border-bottom: 1px solid #ddd;" />
<th style="border-bottom: 1px solid #ddd;" />
<th style="border-bottom: 1px solid #ddd;" />
<th style="border-bottom: 1px solid #ddd;" />
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each $rows as scan}
<tr data-rowid="scan_{scan.id}">
<td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900">
{scan.id}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<a
href="../runners/{scan.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.runner.firstname}
{scan.runner.middlename || ''}
{scan.runner.lastname}</a>
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{scan.runner.firstname}
{scan.runner.middlename || ""}
{scan.runner.lastname}</a
>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900">
{#if scan.distance < 1000}
{scan.distance}m
{:else}{scan.distance / 1000}km{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900">
{#if scan.track}
<a
href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.track.name}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{scan.track.name}
</a>
{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<td class="px-6 py-4 whitespace-nowrap text-left">
{#if scan.responseType === "TRACKSCAN"}
<div class="text-sm font-medium text-gray-900">
{format_laptime(scan.lapTime)}
</div>
{:else}
<div class="text-sm font-medium text-gray-900">
{$_('scan-with-fixed-distance')}
{$_("scan-with-fixed-distance")}
</div>
{/if}
</td>
@ -127,23 +142,30 @@
<div class="flex items-center">
{#if scan.valid}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('valid')}</span>
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("valid")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('invalid')}</span>
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("invalid")}</span
>
{/if}
</div>
</td>
{#if active_deletes[scan.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button
on:click={() => {
active_deletes[scan.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button
on:click={() => {
ScanService.scanControllerRemove(scan.id, false).then(
@ -152,44 +174,51 @@
(obj) => obj.id !== scan.id
);
Toastify({
text: 'Scan deleted',
text: "Scan deleted",
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
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">
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a
href="./{scan.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:DELETE')}
class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:DELETE")}
<button
on:click={() => {
active_deletes[scan.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
{/each}
</tbody>
</table>
</Datatable>
</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>
<b class="capitalize">{$_("general_promise_error")}</b>
{error}
</span>
</div>

View File

@ -0,0 +1,50 @@
<script>
export let handler;
let filterValue = "";
</script>
<th>
<input
on:input={() => {
setTimeout(() => {
const v = filterValue.toLowerCase();
handler.filter(v, (c) => {
if (v.startsWith("#")) {
return `#${c.runner?.id}`;
}
if (c.runner) {
let runnerName = `${c.runner.firstname} ${c.runner.lastname}`;
if (c.runner.middlename) {
runnerName = `${c.runner.firstname} ${c.runner.middlename} ${c.runner.lastname}`;
}
runnerName = runnerName.toLowerCase();
return runnerName;
}
return "";
});
}, 150);
}}
placeholder="Filter"
bind:value={filterValue}
type="text"
name="runnerfilter"
id="runnerfilter"
/>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
input {
margin: -1px 0 0 0;
padding: 0;
width: 100%;
height: 24px;
border: none;
text-align: left;
background: inherit;
outline: 0;
font-size: 14px;
}
</style>