Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack

This commit is contained in:
Nicolai Ort 2023-04-12 20:20:31 +02:00
commit adec38b50b
Signed by: niggl
GPG Key ID: 13AFA55AF62F269F
3 changed files with 214 additions and 233 deletions

View File

@ -214,7 +214,7 @@
/> />
</th> </th>
{#each headerGroup.headers as header} {#each headerGroup.headers as header}
<TableHeader {header}></TableHeader> <TableHeader {header} />
{/each} {/each}
</tr> </tr>
{/each} {/each}
@ -245,7 +245,6 @@
</table> </table>
</div> </div>
<div class="h-2" /> <div class="h-2" />
{/if} {/if}
{/if} {/if}
<TableBottom {table} {selected}></TableBottom> <TableBottom {table} {selected} />

View File

@ -2,19 +2,42 @@
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { ScanService, TrackService } from "@odit/lfk-client-js"; import { ScanService, TrackService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { onMount } from "svelte";
import { writable } from "svelte/store";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import TableBottom from "../shared/TableBottom.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import ScansEmptyState from "./ScansEmptyState.svelte"; import ScansEmptyState from "./ScansEmptyState.svelte";
import ThFilterRunner from "./ThFilterRunner.svelte"; import InputElement from "../shared/InputElement.svelte";
import ThFilterTrack from "./ThFilterTrack.svelte"; import TableActions from "../shared/TableActions.svelte";
$: selectedScans =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: active_delete = undefined;
$: active_deletes = []; $: active_deletes = [];
export let current_scans = []; export let current_scans = [];
const handler = new DataHandler(current_scans, { rowsPerPage: 20 });
const rows = handler.getRows();
const scans_promise = ScanService.scanControllerGetAll().then((val) => { const scans_promise = ScanService.scanControllerGetAll().then((val) => {
current_scans = val; current_scans = val;
handler.setRows(val); // handler.setRows(val);
current_scans = val;
options.update((options) => ({
...options,
data: current_scans,
}));
}); });
$: allTracks = []; let allTracks = [];
TrackService.trackControllerGetAll().then((val) => { TrackService.trackControllerGetAll().then((val) => {
allTracks = val; allTracks = val;
}); });
@ -38,6 +61,85 @@
Math.floor(laptime / 60) * 60 Math.floor(laptime / 60) * 60
}`; }`;
} }
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "runner",
header: () => $_("runner"),
cell: (info) => {
const runner = info.getValue();
return `#${runner.id} - ${runner.firstname} ${runner.lastname}`;
},
enableColumnFilter: true,
},
{
accessorKey: "lapTime",
header: () => $_("laptime"),
cell: (info) => {
return format_laptime(info.getValue());
},
enableColumnFilter: false,
},
{
accessorKey: "distance",
header: () => $_("distance"),
cell: (info) => {
if (info.getValue() < 1000) {
return `${info.getValue()}m`;
}
return `${(info.getValue() / 1000).toFixed(1)}km`;
},
enableColumnFilter: false,
},
{
accessorKey: "track",
header: () => $_("track"),
cell: (info) => {
const track = info.getValue();
return track.name;
},
enableColumnFilter: true,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_delete =
current_scans[
current_scans.findIndex((r) => r.id == info.row.original.id)
];
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes("SCAN:DELETE"),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
</script> </script>
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")} {#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")}
@ -53,171 +155,51 @@
{#if current_scans.length === 0} {#if current_scans.length === 0}
<ScansEmptyState /> <ScansEmptyState />
{:else} {:else}
<div <div class="overflow-x-auto">
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll" <table class="w-full">
> <thead>
<table class="divide-y divide-gray-200 w-full"> {#each $table.getHeaderGroups() as headerGroup}
<thead class="bg-gray-50"> <tr class="select-none">
<tr> <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<Th {handler} orderBy="id">ID</Th> <InputElement
<Th {handler}> type="checkbox"
{$_("runner")} checked={$table.getIsAllRowsSelected()}
</Th> indeterminate={$table.getIsSomeRowsSelected()}
<Th {handler}> on:change={() => $table.toggleAllRowsSelected()}
{$_("distance")} />
</Th> </th>
<Th {handler}> {#each headerGroup.headers as header}
{$_("track")} <TableHeader {header} />
</Th> {/each}
<Th {handler}> </tr>
{$_("laptime")} {/each}
</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;" />
<ThFilterTrack tracks={allTracks} {handler} />
<!-- <th style="border-bottom: 1px solid #ddd;" /> -->
<th style="border-bottom: 1px solid #ddd;" />
<th style="border-bottom: 1px solid #ddd;" />
<!-- TODO: filter status -->
<th style="border-bottom: 1px solid #ddd;" />
</tr>
</thead> </thead>
<tbody class="divide-y divide-gray-200"> <tbody>
{#each $rows as scan} {#each $table.getRowModel().rows as row}
<tr data-rowid="scan_{scan.id}"> <tr>
<td class="px-6 py-4 whitespace-nowrap text-left"> <td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<div class="text-sm font-medium text-gray-900"> <InputElement
{scan.id} type="checkbox"
</div> checked={row.getIsSelected()}
on:change={() => row.toggleSelected()}
/>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> {#each row.getVisibleCells() as cell}
<div class="flex items-center"> <td>
<a <svelte:component
href="../runners/{scan.runner.id}" this={flexRender(
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800" cell.column.columnDef.cell,
>{scan.runner.firstname} cell.getContext()
{scan.runner.middlename || ""} )}
{scan.runner.lastname}</a />
>
</div>
</td>
<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}
</a>
{/if}
</div>
</td>
<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")}
</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<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
>
{:else}
<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"
>
<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
>
<button
on:click={() => {
ScanService.scanControllerRemove(scan.id, false).then(
(resp) => {
current_scans = current_scans.filter(
(obj) => obj.id !== scan.id
);
Toastify({
text: "Scan deleted",
duration: 500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
</td> </td>
{:else} {/each}
<td
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")}
<button
on:click={() => {
active_deletes[scan.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
</td>
{/if}
</tr> </tr>
{/each} {/each}
</tbody> </tbody>
</table> </table>
</div> </div>
<TableBottom {table} {selected} />
{/if} {/if}
{:catch error} {:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">

View File

@ -1,71 +1,71 @@
<script> <script>
export let table;
export let table; export let selected;
export let selected;
</script> </script>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
class="border rounded p-1" class="border rounded p-1"
on:click={() => $table.setPageIndex(0)} on:click={() => $table.setPageIndex(0)}
disabled={!$table.getCanPreviousPage()} disabled={!$table.getCanPreviousPage()}
> >
{"<<"} {"<<"}
</button> </button>
<button <button
class="border rounded p-1" class="border rounded p-1"
on:click={() => $table.previousPage()} on:click={() => $table.previousPage()}
disabled={!$table.getCanPreviousPage()} disabled={!$table.getCanPreviousPage()}
> >
{"<"} {"<"}
</button> </button>
<button <button
class="border rounded p-1" class="border rounded p-1"
on:click={() => $table.nextPage()} on:click={() => $table.nextPage()}
disabled={!$table.getCanNextPage()} disabled={!$table.getCanNextPage()}
> >
{">"} {">"}
</button> </button>
<button <button
class="border rounded p-1" class="border rounded p-1"
on:click={() => $table.setPageIndex($table.getPageCount() - 1)} on:click={() => $table.setPageIndex($table.getPageCount() - 1)}
disabled={!$table.getCanNextPage()} disabled={!$table.getCanNextPage()}
> >
{">>"} {">>"}
</button> </button>
<span class="flex items-center gap-1"> <span class="flex items-center gap-1">
<div>Page</div> <div>Page</div>
<strong> <strong>
{$table.getState().pagination.pageIndex + 1} of{" "} {$table.getState().pagination.pageIndex + 1} of{" "}
{$table.getPageCount()} {$table.getPageCount()}
</strong> </strong>
</span> </span>
<span class="flex items-center gap-1"> <span class="flex items-center gap-1">
| Go to page: | Go to page:
<input <input
type="number" type="number"
defaultValue={$table.getState().pagination.pageIndex + 1} defaultValue={$table.getState().pagination.pageIndex + 1}
on:change={(e) => { on:change={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0; const page = e.target.value ? Number(e.target.value) - 1 : 0;
$table.setPageIndex(page); $table.setPageIndex(page);
}}
class="border p-1 rounded w-16"
/>
</span>
<select
value={$table.getState().pagination.pageSize}
on:input={(e) => {
const ps = Number(e.target.value);
console.log({ ps });
$table.setPageSize(Number(e.target.value));
}} }}
> class="border p-1 rounded w-16"
{#each [25, 50, 100, 250, 500] as pageSize} />
<option value={pageSize}>{pageSize}</option> </span>
{/each} <select
</select> value={$table.getState().pagination.pageSize}
</div> on:input={(e) => {
<!-- <pre>{JSON.stringify($table.getState(), null, 2)}</pre> --> const ps = Number(e.target.value);
<div> console.log({ ps });
{Object.keys(selected).length} of{" "} $table.setPageSize(Number(e.target.value));
{$table.getPreFilteredRowModel().rows.length} Total Rows Selected }}
</div> >
{#each [25, 50, 100, 250, 500] as pageSize}
<option value={pageSize}>{pageSize}</option>
{/each}
</select>
</div>
<!-- <pre>{JSON.stringify($table.getState(), null, 2)}</pre> -->
<div>
{Object.keys(selected).length} of{" "}
{$table.getPreFilteredRowModel().rows.length} Total Rows Selected
</div>