This commit is contained in:
95
src/app/admin/users/CreateOrEditPopup.svelte
Normal file
95
src/app/admin/users/CreateOrEditPopup.svelte
Normal file
@@ -0,0 +1,95 @@
|
||||
<script lang="ts">
|
||||
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
|
||||
import Input from '@components/input/Input.svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import type { User } from './types.ts';
|
||||
import { userCreateOrEditPopupState } from './state.ts';
|
||||
|
||||
// html bindings
|
||||
let modal: HTMLDialogElement;
|
||||
let modalForm: HTMLFormElement;
|
||||
|
||||
// input
|
||||
let action = $state<'create' | 'edit' | null>(null);
|
||||
let user = $state({} as User);
|
||||
let onUpdate = $state((_: User) => {});
|
||||
|
||||
// lifecycle
|
||||
const cancel = userCreateOrEditPopupState.subscribe((value) => {
|
||||
if (value && 'create' in value) {
|
||||
action = 'create';
|
||||
user = {
|
||||
id: -1,
|
||||
username: '',
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
birthday: new Date().toISOString().slice(0, 10),
|
||||
telephone: '',
|
||||
uuid: ''
|
||||
};
|
||||
onUpdate = value?.create.onUpdate;
|
||||
modal.show();
|
||||
} else if (value && 'edit' in value) {
|
||||
action = 'edit';
|
||||
user = value.edit.user;
|
||||
onUpdate = value.edit.onUpdate;
|
||||
modal.show();
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(cancel);
|
||||
|
||||
// texts
|
||||
const texts = {
|
||||
create: {
|
||||
title: 'Nutzer erstellen',
|
||||
buttonTitle: 'Erstellen',
|
||||
confirmPopupTitle: 'Nutzer erstellen?',
|
||||
confirmPopupMessage: 'Sollen der neue Nutzer erstellt werden?'
|
||||
},
|
||||
edit: {
|
||||
title: 'Nutzer bearbeiten',
|
||||
buttonTitle: 'Speichern',
|
||||
confirmPopupTitle: 'Änderunge speichern?',
|
||||
confirmPopupMessage: 'Sollen die Änderungen gespeichert werden?'
|
||||
},
|
||||
null: {}
|
||||
};
|
||||
|
||||
// callbacks
|
||||
function onSaveButtonClick(e: Event) {
|
||||
e.preventDefault();
|
||||
$confirmPopupState = {
|
||||
title: texts[action!].confirmPopupTitle,
|
||||
message: texts[action!].confirmPopupMessage,
|
||||
onConfirm: () => {
|
||||
modalForm.submit();
|
||||
onUpdate(user);
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog class="modal" bind:this={modal}>
|
||||
<form method="dialog" class="modal-box" bind:this={modalForm}>
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
<div class="space-y-5">
|
||||
<h3 class="text-xl font-geist font-bold">{texts[action!].title}</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 w-full sm:w-fit gap-x-4 gap-y-2">
|
||||
<Input type="text" bind:value={user.firstname} label="Vorname" />
|
||||
<Input type="text" bind:value={user.lastname} label="Nachname" />
|
||||
<Input type="date" bind:value={user.birthday} label="Geburtstag" />
|
||||
<Input type="tel" bind:value={user.telephone} label="Telefonnummer" />
|
||||
<Input type="text" bind:value={user.username} label="Spielername" />
|
||||
<Input type="text" bind:value={user.uuid} label="UUID" />
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-success" onclick={onSaveButtonClick}>{texts[action!].buttonTitle}</button>
|
||||
<button class="btn btn-error">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form method="dialog" class="modal-backdrop bg-[rgba(0,0,0,.3)]">
|
||||
<button class="!cursor-default">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
34
src/app/admin/users/SidebarActions.svelte
Normal file
34
src/app/admin/users/SidebarActions.svelte
Normal file
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import Icon from '@iconify/svelte';
|
||||
import { userCreateOrEditPopupState } from './state.ts';
|
||||
import { addUser, fetchUsers } from './actions.ts';
|
||||
import Input from '@components/input/Input.svelte';
|
||||
|
||||
let usernameFilter = $state<string | null>(null);
|
||||
|
||||
// lifecycle
|
||||
$effect(() => {
|
||||
fetchUsers({ username: usernameFilter });
|
||||
});
|
||||
|
||||
// callbacks
|
||||
async function onNewUserButtonClick() {
|
||||
$userCreateOrEditPopupState = {
|
||||
create: {
|
||||
onUpdate: addUser
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<fieldset class="fieldset border border-base-content/50 rounded-box p-2">
|
||||
<legend class="fieldset-legend">Filter</legend>
|
||||
<Input bind:value={usernameFilter} label="Username" />
|
||||
</fieldset>
|
||||
<div class="divider my-1"></div>
|
||||
<button class="btn btn-soft w-full" onclick={() => onNewUserButtonClick()}>
|
||||
<Icon icon="heroicons:plus-16-solid" />
|
||||
<span>Neuer Nutzer</span>
|
||||
</button>
|
||||
</div>
|
||||
56
src/app/admin/users/Users.svelte
Normal file
56
src/app/admin/users/Users.svelte
Normal file
@@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
import Icon from '@iconify/svelte';
|
||||
import CreateOrEditPopup from './CreateOrEditPopup.svelte';
|
||||
import type { User } from './types.ts';
|
||||
import { userCreateOrEditPopupState, users } from './state.ts';
|
||||
import { editUser } from './actions.ts';
|
||||
import SortableTr from '@components/admin/table/SortableTr.svelte';
|
||||
import SortableTh from '@components/admin/table/SortableTh.svelte';
|
||||
|
||||
// callbacks
|
||||
async function onUserEditButtonClick(user: User) {
|
||||
$userCreateOrEditPopupState = {
|
||||
edit: {
|
||||
user: user,
|
||||
onUpdate: editUser
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="h-screen overflow-x-auto">
|
||||
<table class="table table-pin-rows">
|
||||
<thead>
|
||||
<SortableTr data={users}>
|
||||
<SortableTh style="width: 5%">#</SortableTh>
|
||||
<SortableTh style="width: 15%" key="firstname">Vorname</SortableTh>
|
||||
<SortableTh style="width: 15%" key="lastname">Nachname</SortableTh>
|
||||
<SortableTh style="width: 5%" key="birthday">Geburtstag</SortableTh>
|
||||
<SortableTh style="width: 12%" key="phone">Telefon</SortableTh>
|
||||
<SortableTh style="width: 20%" key="username">Username</SortableTh>
|
||||
<SortableTh style="width: 23%">UUID</SortableTh>
|
||||
<SortableTh style="width: 5%"></SortableTh>
|
||||
</SortableTr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each $users as user, i (user.id)}
|
||||
<tr class="hover:bg-base-200">
|
||||
<td>{i + 1}</td>
|
||||
<td>{user.firstname}</td>
|
||||
<td>{user.lastname}</td>
|
||||
<td>{user.birthday}</td>
|
||||
<td>{user.telephone}</td>
|
||||
<td>{user.username}</td>
|
||||
<td>{user.uuid}</td>
|
||||
<td>
|
||||
<button class="cursor-pointer" onclick={() => onUserEditButtonClick(user)}>
|
||||
<Icon icon="heroicons:pencil-square" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<CreateOrEditPopup />
|
||||
56
src/app/admin/users/actions.ts
Normal file
56
src/app/admin/users/actions.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { actions } from 'astro:actions';
|
||||
import { users } from './state.ts';
|
||||
import type { User } from './types.ts';
|
||||
import { actionErrorPopup } from '@util/action.ts';
|
||||
|
||||
export async function fetchUsers(options?: { username?: string | null }) {
|
||||
const { data, error } = await actions.user.users({ username: options?.username });
|
||||
if (error) {
|
||||
actionErrorPopup(error);
|
||||
return;
|
||||
}
|
||||
|
||||
users.set(data.users);
|
||||
}
|
||||
|
||||
export async function addUser(user: User) {
|
||||
const { data, error } = await actions.user.addUser({
|
||||
username: user.username,
|
||||
firstname: user.firstname,
|
||||
lastname: user.lastname,
|
||||
birthday: user.birthday,
|
||||
telephone: user.telephone,
|
||||
uuid: user.uuid
|
||||
});
|
||||
if (error) {
|
||||
actionErrorPopup(error);
|
||||
return;
|
||||
}
|
||||
|
||||
users.update((old) => {
|
||||
old.push(Object.assign(user, { id: data.id }));
|
||||
return old;
|
||||
});
|
||||
}
|
||||
|
||||
export async function editUser(user: User) {
|
||||
const { error } = await actions.user.editUser({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
firstname: user.firstname,
|
||||
lastname: user.lastname,
|
||||
birthday: user.birthday,
|
||||
telephone: user.telephone,
|
||||
uuid: user.uuid
|
||||
});
|
||||
if (error) {
|
||||
actionErrorPopup(error);
|
||||
return;
|
||||
}
|
||||
|
||||
users.update((old) => {
|
||||
const index = old.findIndex((a) => a.id == user.id);
|
||||
old[index] = user;
|
||||
return old;
|
||||
});
|
||||
}
|
||||
6
src/app/admin/users/state.ts
Normal file
6
src/app/admin/users/state.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { UserCreateOrEditPopupState, Users } from './types.ts';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const users = writable<Users>([]);
|
||||
|
||||
export const userCreateOrEditPopupState = writable<UserCreateOrEditPopupState>(null);
|
||||
9
src/app/admin/users/types.ts
Normal file
9
src/app/admin/users/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { type ActionReturnType, actions } from 'astro:actions';
|
||||
|
||||
export type Users = Exclude<ActionReturnType<typeof actions.user.users>['data'], undefined>['users'];
|
||||
export type User = Users[0];
|
||||
|
||||
export type UserCreateOrEditPopupState =
|
||||
| { create: { onUpdate: (user: User) => void } }
|
||||
| { edit: { user: User; onUpdate: (user: User) => void } }
|
||||
| null;
|
||||
Reference in New Issue
Block a user