refactor admin crud popups
All checks were successful
deploy / build-and-deploy (push) Successful in 23s
All checks were successful
deploy / build-and-deploy (push) Successful in 23s
This commit is contained in:
@@ -1,117 +0,0 @@
|
||||
<script lang="ts">
|
||||
import UserSearch from '@components/admin/search/UserSearch.svelte';
|
||||
import Input from '@components/input/Input.svelte';
|
||||
import type { Team } from '@app/admin/teams/types.ts';
|
||||
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
|
||||
|
||||
// html bindings
|
||||
let modal: HTMLDialogElement;
|
||||
let modalForm: HTMLFormElement;
|
||||
|
||||
// types
|
||||
interface Props {
|
||||
popupTitle: string;
|
||||
submitButtonTitle: string;
|
||||
confirmPopupTitle: string;
|
||||
confirmPopupMessage: string;
|
||||
|
||||
team: Team | null;
|
||||
|
||||
open: boolean;
|
||||
|
||||
onSubmit: (team: Team) => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
// inputs
|
||||
let { popupTitle, submitButtonTitle, confirmPopupTitle, confirmPopupMessage, team, open, onSubmit, onClose }: Props =
|
||||
$props();
|
||||
|
||||
// states
|
||||
let name = $state<string | null>(team?.name ?? null);
|
||||
let color = $state<string | null>(team?.color ?? '#000000');
|
||||
let lastJoined = $state<string | null>(team?.lastJoined ?? null);
|
||||
let memberOne = $state<Team['memberOne']>(team?.memberOne ?? ({ username: null } as unknown as Team['memberOne']));
|
||||
let memberTwo = $state<Team['memberOne']>(team?.memberTwo ?? ({ username: null } as unknown as Team['memberOne']));
|
||||
|
||||
let submitEnabled = $derived(!!(name && color && memberOne.username && memberTwo.username));
|
||||
|
||||
// lifecycle
|
||||
$effect(() => {
|
||||
if (open) modal.show();
|
||||
});
|
||||
|
||||
// callbacks
|
||||
async function onSaveButtonClick(e: Event) {
|
||||
e.preventDefault();
|
||||
$confirmPopupState = {
|
||||
title: confirmPopupTitle,
|
||||
message: confirmPopupMessage,
|
||||
onConfirm: () => {
|
||||
modalForm.submit();
|
||||
onSubmit({
|
||||
id: team?.id ?? -1,
|
||||
name: name!,
|
||||
color: color!,
|
||||
lastJoined: lastJoined!,
|
||||
memberOne: memberOne!,
|
||||
memberTwo: memberTwo!
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function onCancelButtonClick(e: Event) {
|
||||
e.preventDefault();
|
||||
modalForm.submit();
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog class="modal" bind:this={modal} onclose={() => setTimeout(() => onClose?.(), 300)}>
|
||||
<form method="dialog" class="modal-box overflow-visible" bind:this={modalForm}>
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={onCancelButtonClick}>✕</button>
|
||||
<div class="space-y-5">
|
||||
<h3 class="text-xl font-geist font-bold">{popupTitle}</h3>
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<Input type="color" label="Farbe" bind:value={color} required />
|
||||
<Input type="text" label="Name" bind:value={name} required />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<UserSearch
|
||||
label="Spieler 1"
|
||||
onSubmit={(user) => {
|
||||
if (user) memberOne = user;
|
||||
}}
|
||||
bind:value={memberOne.username}
|
||||
required
|
||||
mustMatch
|
||||
/>
|
||||
<UserSearch
|
||||
label="Spieler 2"
|
||||
onSubmit={(user) => {
|
||||
if (user) memberTwo = user;
|
||||
}}
|
||||
bind:value={memberTwo.username}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<Input type="datetime-local" label="Zuletzt gejoined" bind:value={lastJoined}></Input>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-success"
|
||||
class:disabled={!submitEnabled}
|
||||
disabled={!submitEnabled}
|
||||
onclick={onSaveButtonClick}>{submitButtonTitle}</button
|
||||
>
|
||||
<button class="btn btn-error" type="button" onclick={onCancelButtonClick}>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>
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import Input from '@components/input/Input.svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
import { addTeam, fetchTeams } from './actions.ts';
|
||||
import CreateOrEditPopup from '@app/admin/teams/CreateOrEditPopup.svelte';
|
||||
import CrudPopup from '@components/admin/popup/CrudPopup.svelte';
|
||||
import { addTeam, fetchTeams } from '@app/admin/teams/teams.ts';
|
||||
|
||||
// states
|
||||
let teamNameFilter = $state<string | null>(null);
|
||||
let memberUsernameFilter = $state<string | null>(null);
|
||||
|
||||
let newTeamPopupOpen = $state(false);
|
||||
let createPopupOpen = $state(false);
|
||||
|
||||
// lifecycle
|
||||
$effect(() => {
|
||||
@@ -23,21 +23,51 @@
|
||||
<Input bind:value={memberUsernameFilter} label="Spieler Username" />
|
||||
</fieldset>
|
||||
<div class="divider my-1"></div>
|
||||
<button class="btn btn-soft w-full" onclick={() => (newTeamPopupOpen = true)}>
|
||||
<button class="btn btn-soft w-full" onclick={() => (createPopupOpen = true)}>
|
||||
<Icon icon="heroicons:plus-16-solid" />
|
||||
<span>Neues Team</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#key newTeamPopupOpen}
|
||||
<CreateOrEditPopup
|
||||
popupTitle="Neues Team"
|
||||
submitButtonTitle="Team erstellen"
|
||||
confirmPopupTitle="Team erstellen"
|
||||
confirmPopupMessage="Bist du sicher, dass du das Team erstellen möchtest?"
|
||||
team={null}
|
||||
open={newTeamPopupOpen}
|
||||
onSubmit={addTeam}
|
||||
onClose={() => (newTeamPopupOpen = false)}
|
||||
/>
|
||||
{/key}
|
||||
<CrudPopup
|
||||
texts={{
|
||||
title: 'Team erstellen',
|
||||
submitButtonTitle: 'Erstellen',
|
||||
confirmPopupTitle: 'Team erstellen?',
|
||||
confirmPopupMessage: 'Sollen das neue Team erstellt werden?'
|
||||
}}
|
||||
target={null}
|
||||
keys={[
|
||||
[
|
||||
{ key: 'name', type: 'text', label: 'Name', options: { required: true } },
|
||||
{ key: 'color', type: 'color', label: 'Farbe', options: { required: true } }
|
||||
],
|
||||
[
|
||||
{
|
||||
key: 'memberOne',
|
||||
type: 'user-search',
|
||||
label: 'Spieler 1',
|
||||
default: { id: null, username: '' },
|
||||
options: { required: true, validate: (user) => !!user.username }
|
||||
},
|
||||
{
|
||||
key: 'memberTwo',
|
||||
type: 'user-search',
|
||||
label: 'Spieler 2',
|
||||
default: { id: null, username: '' },
|
||||
options: { required: true, validate: (user) => !!user.username }
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
key: 'lastJoined',
|
||||
type: 'datetime-local',
|
||||
label: 'Zuletzt gejoined',
|
||||
default: null,
|
||||
options: { convert: (date) => (date ? date : null) }
|
||||
}
|
||||
]
|
||||
]}
|
||||
onSubmit={addTeam}
|
||||
bind:open={createPopupOpen}
|
||||
/>
|
||||
|
||||
@@ -1,65 +1,72 @@
|
||||
<script lang="ts">
|
||||
import { teams } from './state.ts';
|
||||
import type { Team } from './types.ts';
|
||||
import { editTeam } from './actions.ts';
|
||||
import Icon from '@iconify/svelte';
|
||||
import SortableTr from '@components/admin/table/SortableTr.svelte';
|
||||
import SortableTh from '@components/admin/table/SortableTh.svelte';
|
||||
import CreateOrEditPopup from '@app/admin/teams/CreateOrEditPopup.svelte';
|
||||
import { addTeam, teams } from '@app/admin/teams/teams.ts';
|
||||
import DataTable from '@components/admin/table/DataTable.svelte';
|
||||
import CrudPopup from '@components/admin/popup/CrudPopup.svelte';
|
||||
|
||||
// state
|
||||
let editTeamPopupTeam = $state<Team | null>(null);
|
||||
let editPopupTeam = $state(null);
|
||||
let editPopupOpen = $derived(!!editPopupTeam);
|
||||
|
||||
// lifecycle
|
||||
$effect(() => {
|
||||
if (!editPopupOpen) editPopupTeam = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="h-screen overflow-x-auto">
|
||||
<table class="table table-pin-rows">
|
||||
<thead>
|
||||
<SortableTr data={teams}>
|
||||
<SortableTh style="width: 5%">#</SortableTh>
|
||||
<SortableTh style="width: 5%">Farbe</SortableTh>
|
||||
<SortableTh style="width: 25%" key="name">Name</SortableTh>
|
||||
<SortableTh style="width: 30%" key="memberOne.username">Spieler 1</SortableTh>
|
||||
<SortableTh style="width: 30%" key="memberTwo.username">Spieler 2</SortableTh>
|
||||
<SortableTh style="width: 5%"></SortableTh>
|
||||
</SortableTr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each $teams as team, i (team.id)}
|
||||
<tr class="hover:bg-base-200">
|
||||
<td>{i + 1}</td>
|
||||
<td>
|
||||
<div class="rounded-sm w-3 h-3" style="background-color: {team.color}"></div>
|
||||
</td>
|
||||
<td>{team.name}</td>
|
||||
{#if team.memberOne.id != null}
|
||||
<td>{team.memberOne.username}</td>
|
||||
{:else}
|
||||
<td class="text-base-content/30">{team.memberOne.username}</td>
|
||||
{/if}
|
||||
{#if team.memberTwo.id != null}
|
||||
<td>{team.memberTwo.username}</td>
|
||||
{:else}
|
||||
<td class="text-base-content/30">{team.memberTwo.username}</td>
|
||||
{/if}
|
||||
<td>
|
||||
<button class="cursor-pointer" onclick={() => (editTeamPopupTeam = team)}>
|
||||
<Icon icon="heroicons:pencil-square" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{#snippet color(value: string)}
|
||||
<div class="rounded-sm w-3 h-3" style="background-color: {value}"></div>
|
||||
{/snippet}
|
||||
|
||||
{#key editTeamPopupTeam}
|
||||
<CreateOrEditPopup
|
||||
popupTitle="Team bearbeiten"
|
||||
submitButtonTitle="Team bearbeiten"
|
||||
confirmPopupTitle="Team bearbeiten"
|
||||
confirmPopupMessage="Bist du sicher, dass du das Team bearbeiten möchtest?"
|
||||
team={editTeamPopupTeam}
|
||||
open={editTeamPopupTeam != null}
|
||||
onSubmit={editTeam}
|
||||
/>
|
||||
{/key}
|
||||
<DataTable
|
||||
data={teams}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'color', label: 'Farbe', width: 5, transform: color },
|
||||
{ key: 'name', label: 'Name', width: 25 },
|
||||
{ key: 'memberOne.username', label: 'Spieler 1', width: 30 },
|
||||
{ key: 'memberTwo.username', label: 'Spieler 2', width: 30 }
|
||||
]}
|
||||
onEdit={(team) => (editPopupTeam = team)}
|
||||
/>
|
||||
|
||||
<CrudPopup
|
||||
texts={{
|
||||
title: 'Team bearbeiten',
|
||||
submitButtonTitle: 'Speichern',
|
||||
confirmPopupTitle: 'Änderungen speichern?',
|
||||
confirmPopupMessage: 'Sollen die Änderungen gespeichert werden?'
|
||||
}}
|
||||
target={editPopupTeam}
|
||||
keys={[
|
||||
[
|
||||
{ key: 'name', type: 'text', label: 'Name', options: { required: true } },
|
||||
{ key: 'color', type: 'color', label: 'Farbe', options: { required: true } }
|
||||
],
|
||||
[
|
||||
{
|
||||
key: 'memberOne',
|
||||
type: 'user-search',
|
||||
label: 'Spieler 1',
|
||||
options: { required: true, validate: (user) => !!user.username }
|
||||
},
|
||||
{
|
||||
key: 'memberTwo',
|
||||
type: 'user-search',
|
||||
label: 'Spieler 2',
|
||||
default: { id: null, username: null },
|
||||
options: { required: true, validate: (user) => !!user.username }
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
key: 'lastJoined',
|
||||
type: 'datetime-local',
|
||||
label: 'Zuletzt gejoined',
|
||||
default: { id: null, username: null },
|
||||
options: { convert: (date) => (date ? date : null) }
|
||||
}
|
||||
]
|
||||
]}
|
||||
onSubmit={addTeam}
|
||||
bind:open={editPopupOpen}
|
||||
/>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import type { Teams } from './types.ts';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const teams = writable<Teams>([]);
|
||||
@@ -1,8 +1,17 @@
|
||||
import { actions } from 'astro:actions';
|
||||
import { teams } from './state.ts';
|
||||
import type { Team } from './types.ts';
|
||||
import { type ActionReturnType, actions } from 'astro:actions';
|
||||
import { writable } from 'svelte/store';
|
||||
import { actionErrorPopup } from '@util/action.ts';
|
||||
|
||||
// types
|
||||
export type Teams = Exclude<ActionReturnType<typeof actions.team.teams>['data'], undefined>['teams'];
|
||||
export type Team = Teams[0];
|
||||
|
||||
export type Users = Exclude<ActionReturnType<typeof actions.user.users>['data'], undefined>['users'];
|
||||
|
||||
// state
|
||||
export const teams = writable<Teams>([]);
|
||||
|
||||
// actions
|
||||
export async function fetchTeams(name: string | null, username: string | null) {
|
||||
const { data, error } = await actions.team.teams({ name: name, username: username });
|
||||
if (error) {
|
||||
@@ -1,6 +0,0 @@
|
||||
import { type ActionReturnType, actions } from 'astro:actions';
|
||||
|
||||
export type Teams = Exclude<ActionReturnType<typeof actions.team.teams>['data'], undefined>['teams'];
|
||||
export type Team = Teams[0];
|
||||
|
||||
export type Users = Exclude<ActionReturnType<typeof actions.user.users>['data'], undefined>['users'];
|
||||
Reference in New Issue
Block a user