230 lines
7.5 KiB
Svelte
230 lines
7.5 KiB
Svelte
<script lang="ts">
|
|
import type { PageData } from './$types';
|
|
import { Check, NoSymbol, PencilSquare, Trash } from 'svelte-heros-v2';
|
|
import Input from '$lib/components/Input/Input.svelte';
|
|
import Select from '$lib/components/Input/Select.svelte';
|
|
import { env } from '$env/dynamic/public';
|
|
import type { User } from '$lib/server/database';
|
|
import { buttonTriggeredRequest } from '$lib/components/utils';
|
|
import { browser } from '$app/environment';
|
|
import HeaderBar from './HeaderBar.svelte';
|
|
import SortableTr from '$lib/components/Table/SortableTr.svelte';
|
|
import SortableTh from '$lib/components/Table/SortableTh.svelte';
|
|
|
|
export let data: PageData;
|
|
|
|
let currentPageUsers: (typeof User.prototype.dataValues)[] = [];
|
|
let currentPageUsersRequest: Promise<void> = new Promise((resolve) => resolve());
|
|
let usersPerPage = 50;
|
|
let userPage = 0;
|
|
let userFilter = { name: null, playertype: null };
|
|
let userTableContainerElement: HTMLDivElement;
|
|
|
|
function fetchPageUsers(page: number) {
|
|
if (!browser) return;
|
|
|
|
if (userTableContainerElement) userTableContainerElement.scrollTop = 0;
|
|
|
|
// eslint-disable-next-line no-async-promise-executor
|
|
currentPageUsersRequest = new Promise(async (resolve, reject) => {
|
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ...userFilter, limit: usersPerPage, from: usersPerPage * page })
|
|
});
|
|
if (response.ok) {
|
|
currentPageUsers = await response.json();
|
|
resolve();
|
|
} else {
|
|
reject(Error());
|
|
}
|
|
});
|
|
}
|
|
|
|
$: fetchPageUsers(userPage);
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
function fetchFilterPageUsers(_: any) {
|
|
userPage == 0 ? fetchPageUsers(0) : (userPage = 0);
|
|
}
|
|
$: fetchFilterPageUsers(userFilter);
|
|
|
|
let sortKey: string | null = null;
|
|
let sortAsc = false;
|
|
$: if (sortKey != null)
|
|
currentPageUsers = currentPageUsers.sort((entryA, entryB) => {
|
|
const multiplyValue = sortAsc ? -1 : 1;
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore
|
|
const a = entryA[sortKey];
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore
|
|
const b = entryB[sortKey];
|
|
|
|
switch (typeof a) {
|
|
case 'number':
|
|
return (a - b) * multiplyValue;
|
|
case 'string':
|
|
return a.localeCompare(b) * multiplyValue;
|
|
default:
|
|
return (a - b) * multiplyValue;
|
|
}
|
|
});
|
|
|
|
async function updateUser(user: typeof User.prototype.dataValues) {
|
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(user)
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error();
|
|
}
|
|
}
|
|
|
|
async function deleteUser(id: number) {
|
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, {
|
|
method: 'DELETE',
|
|
body: JSON.stringify({
|
|
id: id
|
|
})
|
|
});
|
|
if (response.ok) {
|
|
currentPageUsers.splice(
|
|
currentPageUsers.findIndex((v) => v.id == id),
|
|
1
|
|
);
|
|
currentPageUsers = currentPageUsers;
|
|
} else {
|
|
throw new Error();
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="h-full flex flex-col overflow-hidden">
|
|
<HeaderBar bind:userFilter />
|
|
<hr class="divider my-1 mx-8 border-none" />
|
|
<div class="h-full overflow-scroll" bind:this={userTableContainerElement}>
|
|
<table class="table table-auto">
|
|
<thead>
|
|
<!-- prettier-ignore -->
|
|
<SortableTr class="[&>th]:bg-base-100 [&>th]:z-[1] [&>th]:sticky [&>th]:top-0">
|
|
<th />
|
|
<SortableTh on:sort={(e) => { sortKey = 'firstname'; sortAsc = e.detail.asc }}>Vorname</SortableTh>
|
|
<SortableTh on:sort={(e) => { sortKey = 'lastname'; sortAsc = e.detail.asc }}>Nachname</SortableTh>
|
|
<SortableTh on:sort={(e) => { sortKey = 'birthday'; sortAsc = e.detail.asc }}>Geburtstag</SortableTh>
|
|
<SortableTh on:sort={(e) => { sortKey = 'telephone'; sortAsc = e.detail.asc }}>Telefon</SortableTh>
|
|
<SortableTh on:sort={(e) => { sortKey = 'username'; sortAsc = e.detail.asc }}>Username</SortableTh>
|
|
<SortableTh on:sort={(e) => { sortKey = 'playertype'; sortAsc = e.detail.asc }}>Minecraft Edition</SortableTh>
|
|
<SortableTh on:sort={(e) => { sortKey = 'password'; sortAsc = e.detail.asc }}>Passwort</SortableTh>
|
|
<SortableTh on:sort={(e) => { sortKey = 'uuid'; sortAsc = e.detail.asc }}>UUID</SortableTh>
|
|
<th />
|
|
</SortableTr>
|
|
</thead>
|
|
<tbody>
|
|
{#key currentPageUsersRequest}
|
|
<!-- eslint-disable-next-line @typescript-eslint/no-unused-vars -->
|
|
{#await currentPageUsersRequest then _}
|
|
{#each currentPageUsers as user, i}
|
|
<tr>
|
|
<td>{i + 1 + userPage * usersPerPage}</td>
|
|
<td>
|
|
<Input type="text" bind:value={user.firstname} disabled={!user.edit} size="sm" />
|
|
</td>
|
|
<td>
|
|
<Input type="text" bind:value={user.lastname} disabled={!user.edit} size="sm" />
|
|
</td>
|
|
<td>
|
|
<Input
|
|
type="date"
|
|
value={new Date(user.birthday).toISOString().split('T')[0]}
|
|
on:input={(e) => (user.birthday = e.detail.target.valueAsDate.toISOString())}
|
|
disabled={!user.edit}
|
|
size="sm"
|
|
/>
|
|
</td>
|
|
<td>
|
|
<Input type="tel" bind:value={user.telephone} disabled={!user.edit} size="sm" />
|
|
</td>
|
|
<td>
|
|
<Input type="text" bind:value={user.username} disabled={!user.edit} size="sm" />
|
|
</td>
|
|
<td>
|
|
<Select id="edition" bind:value={user.playertype} disabled={!user.edit} size="sm">
|
|
<option value="java">Java Edition</option>
|
|
<option value="bedrock">Bedrock Edition</option>
|
|
<option value="noauth">Java noauth</option>
|
|
</Select>
|
|
</td>
|
|
<td>
|
|
<Input type="text" bind:value={user.password} disabled={!user.edit} size="sm" />
|
|
</td>
|
|
<td>
|
|
<Input
|
|
id="uuid"
|
|
type="text"
|
|
bind:value={user.uuid}
|
|
disabled={!user.edit}
|
|
size="sm"
|
|
/>
|
|
</td>
|
|
<td>
|
|
<div class="flex gap-1">
|
|
{#if user.edit}
|
|
<button
|
|
class="btn btn-sm btn-square"
|
|
on:click={async (e) => {
|
|
await buttonTriggeredRequest(e, updateUser(user));
|
|
user.edit = false;
|
|
}}
|
|
>
|
|
<Check size="18" />
|
|
</button>
|
|
<button
|
|
class="btn btn-sm btn-square"
|
|
on:click={() => {
|
|
user.edit = false;
|
|
user = user.before;
|
|
}}
|
|
>
|
|
<NoSymbol size="18" />
|
|
</button>
|
|
{:else}
|
|
<button
|
|
class="btn btn-sm btn-square"
|
|
on:click={() => {
|
|
user.before = structuredClone(user);
|
|
user.edit = true;
|
|
}}
|
|
>
|
|
<PencilSquare size="18" />
|
|
</button>
|
|
<button
|
|
class="btn btn-sm btn-square"
|
|
on:click={(e) => buttonTriggeredRequest(e, deleteUser(user.id))}
|
|
>
|
|
<Trash size="18" />
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{/each}
|
|
{/await}
|
|
{/key}
|
|
</tbody>
|
|
</table>
|
|
<div class="flex justify-center items-center mb-2 mt-4 w-full">
|
|
<div class="join">
|
|
<!-- eslint-disable-next-line @typescript-eslint/no-unused-vars -->
|
|
{#each Array(currentPageUsers.length === usersPerPage || userPage > 0 ? Math.ceil(data.count / usersPerPage) || 1 : 1) as _, i}
|
|
<button
|
|
class="join-item btn"
|
|
class:btn-active={i === userPage}
|
|
on:click={() => {
|
|
userPage = i;
|
|
}}>{i + 1}</button
|
|
>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|