make admin page mobile friendly
All checks were successful
deploy / build-and-deploy (push) Successful in 23s
All checks were successful
deploy / build-and-deploy (push) Successful in 23s
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Gemini 3 <google-gemini-noreply@google.com>
This commit is contained in:
@@ -28,8 +28,8 @@
|
||||
data={admins}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'username', label: 'Username', width: 30 },
|
||||
{ key: 'permissions', label: 'Berechtigungen', width: 60, transform: permissionsBadge }
|
||||
{ key: 'username', label: 'Username' },
|
||||
{ key: 'permissions', label: 'Berechtigungen', transform: permissionsBadge }
|
||||
]}
|
||||
onEdit={(admin) => (editPopupAdmin = admin)}
|
||||
onDelete={onAdminDelete}
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
data={blockedUsers}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'uuid', label: 'UUID', width: 20, sortable: true },
|
||||
{ key: 'comment', label: 'Kommentar', width: 70 }
|
||||
{ key: 'uuid', label: 'UUID', sortable: true },
|
||||
{ key: 'comment', label: 'Kommentar' }
|
||||
]}
|
||||
onEdit={(blockedUser) => (blockedUserEditPopupBlockedUser = blockedUser)}
|
||||
onDelete={onBlockedUserDelete}
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
data={directInvitations}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'url', label: 'Link', width: 50 },
|
||||
{ key: 'user.username', label: 'Registrierter Nutzer', width: 40, sortable: true }
|
||||
{ key: 'url', label: 'Link' },
|
||||
{ key: 'user.username', label: 'Registrierter Nutzer', sortable: true }
|
||||
]}
|
||||
onDelete={onDirectInvitationDelete}
|
||||
/>
|
||||
|
||||
@@ -12,15 +12,18 @@
|
||||
let { feedback }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="relative bg-base-200 rounded-lg w-[calc(100%-1rem)] m-2 flex px-6 py-4 gap-2" hidden={feedback === null}>
|
||||
<div
|
||||
class="relative bg-base-200 rounded-lg w-[calc(100%-1rem)] m-2 flex flex-col lg:flex-row px-4 lg:px-6 py-4 gap-4"
|
||||
hidden={feedback === null}
|
||||
>
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={() => (feedback = null)}>✕</button>
|
||||
<div class="w-96">
|
||||
<div class="w-full lg:w-96">
|
||||
<Input value={feedback?.event} label="Event" readonly />
|
||||
<Input value={feedback?.title} label="Titel" readonly />
|
||||
<Input value={feedback?.username} label="Nutzer" readonly />
|
||||
</div>
|
||||
<div class="divider divider-horizontal"></div>
|
||||
<div class="divider lg:divider-horizontal"></div>
|
||||
<div class="w-full">
|
||||
<Textarea value={feedback?.content} label="Inhalt" rows={9} readonly dynamicWidth />
|
||||
<Textarea value={feedback?.content} label="Inhalt" rows={6} readonly dynamicWidth />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
{dateFormat.format(new Date(value))}
|
||||
{/snippet}
|
||||
|
||||
<div class="h-screen flex flex-col justify-between">
|
||||
<div class="h-full flex flex-col justify-between">
|
||||
<DataTable
|
||||
data={feedbacks}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'event', label: 'Event', width: 10, sortable: true },
|
||||
{ key: 'username', label: 'Nutzer', width: 10, sortable: true },
|
||||
{ key: 'lastChanged', label: 'Datum', width: 10, sortable: true, transform: date },
|
||||
{ key: 'content', label: 'Inhalt', width: 10 }
|
||||
{ key: 'event', label: 'Event', sortable: true },
|
||||
{ key: 'username', label: 'Nutzer', sortable: true },
|
||||
{ key: 'lastChanged', label: 'Datum', sortable: true, transform: date },
|
||||
{ key: 'content', label: 'Inhalt' }
|
||||
]}
|
||||
onClick={(feedback) => (activeFeedback = feedback)}
|
||||
/>
|
||||
|
||||
@@ -89,7 +89,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative bg-base-200 rounded-lg w-[calc(100%-1rem)] m-2 flex px-6 py-4 gap-2" hidden={report === null}>
|
||||
<div
|
||||
class="relative bg-base-200 rounded-lg w-[calc(100%-1rem)] m-2 flex flex-col lg:flex-row px-4 lg:px-6 py-4 gap-4"
|
||||
hidden={report === null}
|
||||
>
|
||||
<div class="absolute right-2 top-2">
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-sm btn-circle btn-ghost">
|
||||
@@ -102,35 +105,38 @@
|
||||
</div>
|
||||
<button class="btn btn-sm btn-circle btn-ghost" onclick={() => (report = null)}>✕</button>
|
||||
</div>
|
||||
<div class="w-[34rem]">
|
||||
<div class="w-full lg:w-[34rem]">
|
||||
<UserSearch value={report?.reporter.username} label="Report Ersteller" readonly mustMatch />
|
||||
<UserSearch
|
||||
value={report?.reported?.username}
|
||||
label="Reporteter Spieler"
|
||||
onSubmit={(user) => (reportedUser = user)}
|
||||
/>
|
||||
<Textarea bind:value={notice} label="Interne Notizen" rows={10} />
|
||||
<Textarea bind:value={notice} label="Interne Notizen" rows={6} />
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<Input value={report?.reason} label="Grund" readonly dynamicWidth />
|
||||
<Textarea value={report?.body} label="Inhalt" readonly dynamicWidth rows={9} />
|
||||
<Textarea value={report?.body} label="Inhalt" readonly dynamicWidth rows={6} />
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">Anhänge</legend>
|
||||
<div class="h-16.5 rounded border border-dashed flex">
|
||||
<div class="min-h-16.5 rounded border border-dashed border-base-content/20 flex flex-wrap p-1">
|
||||
{#each reportAttachments as reportAttachment (reportAttachment.hash)}
|
||||
<div>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="cursor-zoom-in" onclick={() => (previewReportAttachment = reportAttachment)}>
|
||||
<div class="cursor-zoom-in p-1" onclick={() => (previewReportAttachment = reportAttachment)}>
|
||||
{#if reportAttachment.type === 'image'}
|
||||
<img
|
||||
src={location.pathname + '/attachment/' + reportAttachment.hash}
|
||||
alt={reportAttachment.hash}
|
||||
class="w-16 h-16"
|
||||
class="w-16 h-16 object-cover rounded"
|
||||
/>
|
||||
{:else if reportAttachment.type === 'video'}
|
||||
<!-- svelte-ignore a11y_media_has_caption -->
|
||||
<video src={location.pathname + '/attachment/' + reportAttachment.hash} class="w-16 h-16"></video>
|
||||
<video
|
||||
src={location.pathname + '/attachment/' + reportAttachment.hash}
|
||||
class="w-16 h-16 object-cover rounded"
|
||||
></video>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -138,9 +144,9 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="divider divider-horizontal"></div>
|
||||
<div class="flex flex-col w-[42rem]">
|
||||
<Textarea bind:value={statement} label="Öffentliche Report Antwort" dynamicWidth rows={7} />
|
||||
<div class="divider lg:divider-horizontal"></div>
|
||||
<div class="flex flex-col w-full lg:w-[42rem]">
|
||||
<Textarea bind:value={statement} label="Öffentliche Report Antwort" dynamicWidth rows={6} />
|
||||
<Select
|
||||
bind:value={status}
|
||||
values={{ open: 'In Bearbeitung', closed: 'Bearbeitet' }}
|
||||
@@ -150,7 +156,7 @@
|
||||
/>
|
||||
<Select bind:value={strikeReason} values={strikeReasonValues} label="Vergehen" dynamicWidth></Select>
|
||||
<div class="divider mt-0 mb-2"></div>
|
||||
<button class="btn mt-auto" onclick={onSaveButtonClick}>Speichern</button>
|
||||
<button class="btn btn-primary mt-auto" onclick={onSaveButtonClick}>Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import BottomBar from '@app/admin/reports/BottomBar.svelte';
|
||||
import { onMount, untrack } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import DataTable from '@components/admin/table/DataTable.svelte';
|
||||
import { type StrikeReasons, getStrikeReasons, reports } from '@app/admin/reports/reports.ts';
|
||||
|
||||
@@ -49,16 +49,16 @@
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<div class="h-screen flex flex-col justify-between">
|
||||
<div class="h-full flex flex-col justify-between">
|
||||
<DataTable
|
||||
data={reports}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'reason', label: 'Grund', width: 35 },
|
||||
{ key: 'reporter.username', label: 'Report Ersteller', width: 15 },
|
||||
{ key: 'reported.username', label: 'Reporteter Spieler', width: 15 },
|
||||
{ key: 'createdAt', label: 'Datum', width: 15, sortable: true, transform: date },
|
||||
{ key: 'status.status', label: 'Bearbeitungsstatus', width: 10, sortable: true, transform: status }
|
||||
{ key: 'reason', label: 'Grund' },
|
||||
{ key: 'reporter.username', label: 'Report Ersteller' },
|
||||
{ key: 'reported.username', label: 'Reporteter Spieler' },
|
||||
{ key: 'createdAt', label: 'Datum', sortable: true, transform: date },
|
||||
{ key: 'status.status', label: 'Bearbeitungsstatus', sortable: true, transform: status }
|
||||
]}
|
||||
onClick={(report) => (activeReport = report)}
|
||||
/>
|
||||
|
||||
@@ -99,16 +99,16 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col items-center justify-between">
|
||||
<div class="grid grid-cols-2 w-full">
|
||||
<div class="min-h-full flex flex-col items-center justify-between">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 w-full px-4 lg:px-12 gap-8">
|
||||
{#each settingsInput as setting (setting.name)}
|
||||
<div class="mx-12">
|
||||
<div class="divider">{setting.name}</div>
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="flex flex-col">
|
||||
<div class="divider font-bold">{setting.name}</div>
|
||||
<div class="flex flex-col gap-6">
|
||||
{#each setting.entries as entry (entry.name)}
|
||||
<label class="flex justify-between">
|
||||
<span class="mt-[.125rem] text-sm w-1/2">{entry.name}</span>
|
||||
<div class="w-1/2">
|
||||
<label class="flex flex-col sm:flex-row justify-between gap-2">
|
||||
<span class="text-sm font-medium sm:w-1/2">{entry.name}</span>
|
||||
<div class="sm:w-1/2 flex sm:justify-end">
|
||||
{#if entry.type === 'checkbox'}
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -122,7 +122,7 @@
|
||||
{:else if entry.type === 'text'}
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered"
|
||||
class="input input-bordered w-full sm:max-w-xs"
|
||||
onchange={(e) => {
|
||||
entry.onChange(e.currentTarget.value);
|
||||
changes = dynamicSettings.getChanges();
|
||||
@@ -131,7 +131,7 @@
|
||||
/>
|
||||
{:else if entry.type === 'textarea'}
|
||||
<textarea
|
||||
class="textarea"
|
||||
class="textarea textarea-bordered w-full sm:max-w-xs min-h-24"
|
||||
value={entry.value}
|
||||
onchange={(e) => {
|
||||
entry.onChange(e.currentTarget.value);
|
||||
@@ -146,9 +146,9 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div>
|
||||
<div class="py-12">
|
||||
<button
|
||||
class="btn btn-success mt-auto mb-8"
|
||||
class="btn btn-success btn-lg px-12"
|
||||
class:btn-disabled={Object.keys(changes).length === 0}
|
||||
onclick={onSaveSettingsClick}>Speichern</button
|
||||
>
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
data={strikeReasons}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'name', label: 'Name', width: 20 },
|
||||
{ key: 'weight', label: 'Gewichtung', width: 50, sortable: true },
|
||||
{ key: 'id', label: 'Id', width: 20 }
|
||||
{ key: 'name', label: 'Name' },
|
||||
{ key: 'weight', label: 'Gewichtung', sortable: true },
|
||||
{ key: 'id', label: 'Id' }
|
||||
]}
|
||||
onDelete={onBlockedUserDelete}
|
||||
onEdit={(strikeReason) => (editPopupStrikeReason = strikeReason)}
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<fieldset class="fieldset border border-base-200 rounded-box px-4">
|
||||
<legend class="fieldset-legend">Account UUID finder</legend>
|
||||
<div>
|
||||
<div class="flex gap-3">
|
||||
<Input bind:value={username} />
|
||||
<Select bind:value={edition} values={{ java: 'Java', bedrock: 'Bedrock' }} />
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<Input bind:value={username} dynamicWidth />
|
||||
<Select bind:value={edition} values={{ java: 'Java', bedrock: 'Bedrock' }} dynamicWidth />
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<button class="btn w-4/6" class:disabled={!username} onclick={onSubmit}>UUID finden</button>
|
||||
|
||||
@@ -47,13 +47,13 @@
|
||||
data={users}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'firstname', label: 'Vorname', width: 15, sortable: true },
|
||||
{ key: 'lastname', label: 'Nachname', width: 15, sortable: true },
|
||||
{ key: 'birthday', label: 'Geburtstag', width: 5, sortable: true },
|
||||
{ key: 'telephone', label: 'Telefon', width: 12, sortable: true },
|
||||
{ key: 'username', label: 'Username', width: 15, sortable: true },
|
||||
{ key: 'edition', label: 'Edition', width: 5, sortable: true, transform: edition },
|
||||
{ key: 'uuid', label: 'UUID', width: 23 }
|
||||
{ key: 'firstname', label: 'Vorname', sortable: true },
|
||||
{ key: 'lastname', label: 'Nachname', sortable: true },
|
||||
{ key: 'birthday', label: 'Geburtstag', sortable: true },
|
||||
{ key: 'telephone', label: 'Telefon', sortable: true },
|
||||
{ key: 'username', label: 'Username', sortable: true },
|
||||
{ key: 'edition', label: 'Edition', sortable: true, transform: edition },
|
||||
{ key: 'uuid', label: 'UUID' }
|
||||
]}
|
||||
extraActions={blockAction}
|
||||
onEdit={(user) => (editPopupUser = user)}
|
||||
|
||||
Reference in New Issue
Block a user