refactor admin crud popups
All checks were successful
deploy / build-and-deploy (push) Successful in 23s

This commit is contained in:
2025-05-21 17:22:20 +02:00
parent 8b18623232
commit e47268111a
46 changed files with 889 additions and 1041 deletions

View File

@ -1,10 +1,10 @@
<script lang="ts">
import type { Report, ReportStatus, StrikeReasons } from './types.ts';
import type { Report, ReportStatus, StrikeReasons } from './reports.ts';
import Input from '@components/input/Input.svelte';
import Textarea from '@components/input/Textarea.svelte';
import Select from '@components/input/Select.svelte';
import TeamSearch from '@components/admin/search/TeamSearch.svelte';
import { editReportStatus, getReportStatus } from '@app/admin/reports/actions.ts';
import { editReportStatus, getReportStatus } from '@app/admin/reports/reports.ts';
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
// types

View File

@ -1,97 +0,0 @@
<script lang="ts">
import Input from '@components/input/Input.svelte';
import TeamSearch from '@components/admin/search/TeamSearch.svelte';
import Textarea from '@components/input/Textarea.svelte';
import Checkbox from '@components/input/Checkbox.svelte';
import type { Report } from './types.ts';
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
// html bindings
let modal: HTMLDialogElement;
let modalForm: HTMLFormElement;
// types
interface Props {
open: boolean;
onSubmit: (report: Report) => void;
onClose?: () => void;
}
// input
let { open, onSubmit, onClose }: Props = $props();
// form
let reason = $state<string | null>(null);
let body = $state<string | null>(null);
let editable = $state<boolean>(true);
let reporter = $state<Report['reporter'] | null>(null);
let reported = $state<Report['reported'] | null>(null);
let submitEnabled = $derived(!!(reason && reporter));
// lifecycle
$effect(() => {
if (open) modal.show();
});
// callbacks
async function onSaveButtonClick(e: Event) {
e.preventDefault();
$confirmPopupState = {
title: 'Report erstellen',
message: 'Bist du sicher, dass du den Report erstellen möchtest?',
onConfirm: () => {
modalForm.submit();
onSubmit({
id: -1,
reason: reason!,
body: body!,
reporter: reporter!,
reported: reported!,
createdAt: editable ? null : new Date().toISOString(),
status: null
});
}
};
}
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" 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-xt font-geist font-bold">Neuer Report</h3>
<div>
<div class="grid grid-cols-2 gap-4">
<TeamSearch label="Report Team" required mustMatch onSubmit={(team) => (reporter = team)} />
<TeamSearch label="Reportetes Team" mustMatch onSubmit={(team) => (reported = team)} />
</div>
<div class="grid grid-cols-1">
<Input label="Grund" bind:value={reason} required dynamicWidth />
<Textarea label="Inhalt" bind:value={body} rows={5} dynamicWidth />
</div>
<div class="grid grid-cols-1 mt-2">
<Checkbox label="Report kann bearbeitet werden" bind:checked={editable} />
</div>
</div>
<div>
<button
class="btn btn-success"
class:disabled={!submitEnabled}
disabled={!submitEnabled}
onclick={onSaveButtonClick}>Erstellen</button
>
<button class="btn btn-error" 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>

View File

@ -1,11 +1,8 @@
<script lang="ts">
import SortableTr from '@components/admin/table/SortableTr.svelte';
import { reports } from './state.ts';
import type { Report, StrikeReasons } from './types.ts';
import SortableTh from '@components/admin/table/SortableTh.svelte';
import BottomBar from '@app/admin/reports/BottomBar.svelte';
import { onMount } from 'svelte';
import { getStrikeReasons } from '@app/admin/reports/actions.ts';
import DataTable from '@components/admin/table/DataTable.svelte';
import { type StrikeReasons, getStrikeReasons, reports } from '@app/admin/reports/reports.ts';
// states
let strikeReasons = $state<StrikeReasons>([]);
@ -17,33 +14,18 @@
});
</script>
<div class="h-screen overflow-x-auto">
<table class="table table-pin-rows">
<thead>
<SortableTr data={reports}>
<SortableTh style="width: 5%">#</SortableTh>
<SortableTh>Grund</SortableTh>
<SortableTh>Report Team</SortableTh>
<SortableTh>Reportetes Team</SortableTh>
<SortableTh>Datum</SortableTh>
<SortableTh>Bearbeitungsstatus</SortableTh>
<SortableTh style="width: 5%"></SortableTh>
</SortableTr>
</thead>
<tbody>
{#each $reports as report, i (report.id)}
<tr class="hover:bg-base-200" onclick={() => (activeReport = report)}>
<td>{i + 1}</td>
<td>{report.reason}</td>
<td>{report.reporter.name}</td>
<td>{report.reported?.name}</td>
<td>{report.createdAt}</td>
<td>{report.status?.status}</td>
</tr>
{/each}
</tbody>
</table>
</div>
<DataTable
data={reports}
count={true}
keys={[
{ key: 'reason', label: 'Grund' },
{ key: 'reporter.name', label: 'Report Team' },
{ key: 'reported.name', label: 'Reportetes Team' },
{ key: 'createdAt', label: 'Datum' },
{ key: 'report.status?.status', label: 'Bearbeitungsstatus' }
]}
onClick={(report) => (activeReport = report)}
/>
{#key activeReport}
<BottomBar {strikeReasons} report={activeReport} />

View File

@ -1,14 +1,14 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import Input from '@components/input/Input.svelte';
import { addReport, fetchReports } from '@app/admin/reports/actions.ts';
import CreatePopup from '@app/admin/reports/CreatePopup.svelte';
import CrudPopup from '@components/admin/popup/CrudPopup.svelte';
import { addReport, fetchReports } from '@app/admin/reports/reports.ts';
// states
let reporterUsernameFilter = $state<string | null>(null);
let reportedUsernameFilter = $state<string | null>(null);
let newReportPopupOpen = $state(false);
let createPopupOpen = $state(false);
// lifecycle
$effect(() => {
@ -23,12 +23,48 @@
<Input bind:value={reportedUsernameFilter} label="Reporteter Spieler" />
</fieldset>
<div class="divider my-1"></div>
<button class="btn btn-soft w-full" onclick={() => (newReportPopupOpen = true)}>
<button class="btn btn-soft w-full" onclick={() => (createPopupOpen = true)}>
<Icon icon="heroicons:plus-16-solid" />
<span>Neuer Report</span>
</button>
</div>
{#key newReportPopupOpen}
<CreatePopup open={newReportPopupOpen} onSubmit={addReport} onClose={() => (newReportPopupOpen = false)} />
{/key}
<CrudPopup
texts={{
title: 'Report erstellen',
submitButtonTitle: 'Erstellen',
confirmPopupTitle: 'Report erstellen?',
confirmPopupMessage: 'Soll der Report erstellt werden?'
}}
target={null}
keys={[
[
{
key: 'reporter',
type: 'team-search',
label: 'Report Team',
default: { id: null, name: null },
options: { required: true, mustMatch: true, validate: (team) => team?.id != null }
},
{
key: 'reported',
type: 'team-search',
label: 'Reportetes Team',
default: { id: null, name: null },
options: { mustMatch: true, validate: (team) => team?.id != null }
}
],
[{ key: 'reason', type: 'text', label: 'Grund', options: { required: true, dynamicWidth: true } }],
[{ key: 'body', type: 'textarea', label: 'Inhalt', default: null, options: { rows: 5, dynamicWidth: true } }],
[
{
key: 'createdAt',
type: 'checkbox',
label: 'Report kann bearbeitet werden',
options: { convert: (v) => (v ? new Date().toISOString() : null) }
}
]
]}
onSubmit={addReport}
bind:open={createPopupOpen}
/>

View File

@ -1,8 +1,25 @@
import { actions } from 'astro:actions';
import { reports } from './state.ts';
import { type ActionReturnType, actions } from 'astro:actions';
import { writable } from 'svelte/store';
import { actionErrorPopup } from '@util/action.ts';
import type { Report, ReportStatus } from './types.ts';
// types
export type Reports = Exclude<ActionReturnType<typeof actions.report.reports>['data'], undefined>['reports'];
export type Report = Reports[0];
export type ReportStatus = Exclude<
Exclude<ActionReturnType<typeof actions.report.reportStatus>['data'], undefined>['reportStatus'],
null
>;
export type StrikeReasons = Exclude<
ActionReturnType<typeof actions.report.strikeReasons>['data'],
undefined
>['strikeReasons'];
// state
export const reports = writable<Reports>([]);
// actions
export async function fetchReports(reporterUsername: string | null, reportedUsername: string | null) {
const { data, error } = await actions.report.reports({ reporter: reporterUsername, reported: reportedUsername });
if (error) {

View File

@ -1,4 +0,0 @@
import { writable } from 'svelte/store';
import type { Reports } from './types.ts';
export const reports = writable<Reports>([]);

View File

@ -1,14 +0,0 @@
import type { ActionReturnType, actions } from 'astro:actions';
export type Reports = Exclude<ActionReturnType<typeof actions.report.reports>['data'], undefined>['reports'];
export type Report = Reports[0];
export type ReportStatus = Exclude<
Exclude<ActionReturnType<typeof actions.report.reportStatus>['data'], undefined>['reportStatus'],
null
>;
export type StrikeReasons = Exclude<
ActionReturnType<typeof actions.report.strikeReasons>['data'],
undefined
>['strikeReasons'];