205 lines
6.5 KiB
Svelte
205 lines
6.5 KiB
Svelte
<script lang="ts">
|
|
import { Check, NoSymbol, PencilSquare, Plus, 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';
|
|
import NewUserModal from './NewUserModal.svelte';
|
|
import PaginationTableBody from '$lib/components/PaginationTable/PaginationTableBody.svelte';
|
|
|
|
let users: (typeof User.prototype.dataValues)[] = $state([]);
|
|
let usersPerRequest = 25;
|
|
let userFilter: { [k: string]: any } = $state({ name: null, playertype: null });
|
|
|
|
let userTableContainerElement: HTMLDivElement;
|
|
let newUserModal: HTMLDialogElement;
|
|
|
|
async function fetchUsers(extendedFilter?: {
|
|
limit?: number;
|
|
from?: number;
|
|
}): Promise<typeof users> {
|
|
if (!browser) return [];
|
|
|
|
if (userTableContainerElement) userTableContainerElement.scrollTop = 0;
|
|
|
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
...userFilter,
|
|
limit: extendedFilter?.limit ?? usersPerRequest,
|
|
from: extendedFilter?.from ?? users.length
|
|
})
|
|
});
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
async function updateUser(user: typeof User.prototype.dataValues) {
|
|
await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(user)
|
|
});
|
|
}
|
|
|
|
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) {
|
|
users.splice(
|
|
users.findIndex((v) => v.id == id),
|
|
1
|
|
);
|
|
users = users;
|
|
}
|
|
}
|
|
|
|
let userFilterEffectAlreadyRan = false;
|
|
$effect(() => {
|
|
userFilter;
|
|
|
|
if (!userFilterEffectAlreadyRan) {
|
|
userFilterEffectAlreadyRan = true;
|
|
return;
|
|
}
|
|
fetchUsers({ from: 0 }).then((u) => (users = u));
|
|
});
|
|
</script>
|
|
|
|
<div class="h-full flex flex-col overflow-hidden">
|
|
<div class="grid grid-cols-[10fr_1fr_10fr_1fr_10fr]">
|
|
<div></div>
|
|
<div></div>
|
|
<HeaderBar bind:userFilter onUpdate={() => (userFilter = $state.snapshot(userFilter))} />
|
|
<div class="divider divider-horizontal my-auto h-3/4"></div>
|
|
<div class="flex items-center">
|
|
<button class="btn" onclick={() => newUserModal.show()}>
|
|
<Plus />
|
|
<span>Neuer Spieler</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<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></th>
|
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'firstname', asc: e.asc}}}>Vorname</SortableTh>
|
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'lastname', asc: e.asc}}}>Nachname</SortableTh>
|
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'birthday', asc: e.asc}}}>Geburtstag</SortableTh>
|
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'telephone', asc: e.asc}}}>Telefon</SortableTh>
|
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'username', asc: e.asc}}}>Username</SortableTh>
|
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'playertype', asc: e.asc}}}>Minecraft Edition</SortableTh>
|
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'password', asc: e.asc}}}>Passwort</SortableTh>
|
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'uuid', asc: e.asc}}}>UUID</SortableTh>
|
|
<th></th>
|
|
</SortableTr>
|
|
</thead>
|
|
<PaginationTableBody
|
|
onUpdate={async () => {
|
|
await fetchUsers().then((u) => (users = [...users, ...u]));
|
|
}}
|
|
>
|
|
{#each users as user, i}
|
|
<tr>
|
|
<td>{i + 1}</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]}
|
|
oninput={(e) => (user.birthday = e.currentTarget.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"
|
|
onclick={async (e) => {
|
|
await buttonTriggeredRequest(e, updateUser(user));
|
|
user.edit = false;
|
|
}}
|
|
>
|
|
<Check size="18" />
|
|
</button>
|
|
<button
|
|
class="btn btn-sm btn-square"
|
|
onclick={() => {
|
|
user.edit = false;
|
|
users[i] = user.before;
|
|
}}
|
|
>
|
|
<NoSymbol size="18" />
|
|
</button>
|
|
{:else}
|
|
<button
|
|
class="btn btn-sm btn-square"
|
|
onclick={() => {
|
|
user.before = $state.snapshot(user);
|
|
user.edit = true;
|
|
}}
|
|
>
|
|
<PencilSquare size="18" />
|
|
</button>
|
|
<button
|
|
class="btn btn-sm btn-square"
|
|
onclick={(e) => buttonTriggeredRequest(e, deleteUser(user.id))}
|
|
>
|
|
<Trash size="18" />
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{/each}
|
|
</PaginationTableBody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<dialog class="modal" bind:this={newUserModal}>
|
|
<NewUserModal
|
|
onSubmit={(e) => {
|
|
users = [...users, e];
|
|
newUserModal.close();
|
|
}}
|
|
/>
|
|
</dialog>
|