add death to admin ui
This commit is contained in:
@ -128,5 +128,53 @@ export const team = {
|
|||||||
teams: await db.getTeams(input)
|
teams: await db.getTeams(input)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
addDeath: defineAction({
|
||||||
|
input: z.object({
|
||||||
|
deadUserId: z.number(),
|
||||||
|
killerUserId: z.number().nullish(),
|
||||||
|
message: z.string()
|
||||||
|
}),
|
||||||
|
handler: async (input, context) => {
|
||||||
|
Session.actionSessionFromCookies(context.cookies, Permissions.Users);
|
||||||
|
|
||||||
|
const { id } = await db.addDeath(input);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
editDeath: defineAction({
|
||||||
|
input: z.object({
|
||||||
|
id: z.number(),
|
||||||
|
deadUserId: z.number(),
|
||||||
|
killerUserId: z.number().nullish(),
|
||||||
|
message: z.string()
|
||||||
|
}),
|
||||||
|
handler: async (input, context) => {
|
||||||
|
Session.actionSessionFromCookies(context.cookies, Permissions.Users);
|
||||||
|
|
||||||
|
await db.editDeath(input);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
deleteDeath: defineAction({
|
||||||
|
input: z.object({
|
||||||
|
id: z.number()
|
||||||
|
}),
|
||||||
|
handler: async (input, context) => {
|
||||||
|
Session.actionSessionFromCookies(context.cookies, Permissions.Users);
|
||||||
|
|
||||||
|
await db.deleteDeath(input);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
deaths: defineAction({
|
||||||
|
handler: async (_, context) => {
|
||||||
|
Session.actionSessionFromCookies(context.cookies, Permissions.Users);
|
||||||
|
|
||||||
|
return {
|
||||||
|
deaths: await db.getDeaths({})
|
||||||
|
};
|
||||||
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import TeamSearch from '@components/admin/search/TeamSearch.svelte';
|
import TeamSearch from '@components/admin/search/TeamSearch.svelte';
|
||||||
import { editReportStatus, getReportStatus } from '@app/admin/reports/reports.ts';
|
import { editReportStatus, getReportStatus } from '@app/admin/reports/reports.ts';
|
||||||
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
|
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
|
||||||
import Icon from "@iconify/svelte";
|
import Icon from '@iconify/svelte';
|
||||||
|
|
||||||
// html bindings
|
// html bindings
|
||||||
let previewDialogElem: HTMLDialogElement;
|
let previewDialogElem: HTMLDialogElement;
|
||||||
|
49
src/app/admin/teamDeaths/SidebarActions.svelte
Normal file
49
src/app/admin/teamDeaths/SidebarActions.svelte
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { addDeath, fetchDeaths } from '@app/admin/teamDeaths/teamDeaths.ts';
|
||||||
|
import Icon from '@iconify/svelte';
|
||||||
|
import CrudPopup from '@components/admin/popup/CrudPopup.svelte';
|
||||||
|
|
||||||
|
// states
|
||||||
|
let createPopupOpen = $state(false);
|
||||||
|
|
||||||
|
// lifecycle
|
||||||
|
$effect(() => {
|
||||||
|
fetchDeaths();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-soft w-full" onclick={() => (createPopupOpen = true)}>
|
||||||
|
<Icon icon="heroicons:plus-16-solid" />
|
||||||
|
<span>Neuer Spielertod</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CrudPopup
|
||||||
|
texts={{
|
||||||
|
title: 'Spielertod erstellen',
|
||||||
|
submitButtonTitle: 'Erstellen',
|
||||||
|
confirmPopupTitle: 'Spielertod erstellen?',
|
||||||
|
confirmPopupMessage: 'Soll der neue Spielertod erstellt werden?'
|
||||||
|
}}
|
||||||
|
target={null}
|
||||||
|
keys={[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: 'killed',
|
||||||
|
type: 'user-search',
|
||||||
|
label: 'Getöteter Spieler',
|
||||||
|
options: { required: true, validate: (user) => !!user?.id }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'killer',
|
||||||
|
type: 'user-search',
|
||||||
|
label: 'Killer',
|
||||||
|
options: { validate: (user) => (user?.username ? !!user?.id : true) }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[{ key: 'message', type: 'textarea', label: 'Todesnachricht', options: { required: true, dynamicWidth: true } }]
|
||||||
|
]}
|
||||||
|
onSubmit={addDeath}
|
||||||
|
bind:open={createPopupOpen}
|
||||||
|
/>
|
69
src/app/admin/teamDeaths/TeamDeaths.svelte
Normal file
69
src/app/admin/teamDeaths/TeamDeaths.svelte
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
// state
|
||||||
|
import { type Death, deaths, deleteDeath, editDeath } from '@app/admin/teamDeaths/teamDeaths.ts';
|
||||||
|
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
|
||||||
|
import DataTable from '@components/admin/table/DataTable.svelte';
|
||||||
|
import CrudPopup from '@components/admin/popup/CrudPopup.svelte';
|
||||||
|
|
||||||
|
let editPopupDeath = $state(null);
|
||||||
|
let editPopupOpen = $derived(!!editPopupDeath);
|
||||||
|
|
||||||
|
// lifecycle
|
||||||
|
$effect(() => {
|
||||||
|
if (!editPopupOpen) editPopupDeath = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// callbacks
|
||||||
|
function onDeathDelete(death: Death) {
|
||||||
|
$confirmPopupState = {
|
||||||
|
title: 'Tod löschen?',
|
||||||
|
message: 'Soll der Tod wirklich gelöscht werden?',
|
||||||
|
onConfirm: () => deleteDeath(death)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#snippet username(user?: { id: number; username: string })}
|
||||||
|
{user?.username}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
data={deaths}
|
||||||
|
count={true}
|
||||||
|
keys={[
|
||||||
|
{ key: 'killed', label: 'Getöteter Spieler', width: 20, transform: username },
|
||||||
|
{ key: 'killer', label: 'Killer', width: 20, transform: username },
|
||||||
|
{ key: 'message', label: 'Todesnachricht', width: 50 }
|
||||||
|
]}
|
||||||
|
onEdit={(death) => (editPopupDeath = death)}
|
||||||
|
onDelete={onDeathDelete}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CrudPopup
|
||||||
|
texts={{
|
||||||
|
title: 'Tod bearbeiten',
|
||||||
|
submitButtonTitle: 'Speichern',
|
||||||
|
confirmPopupTitle: 'Änderungen speichern?',
|
||||||
|
confirmPopupMessage: 'Sollen die Änderungen gespeichert werden?'
|
||||||
|
}}
|
||||||
|
target={editPopupDeath}
|
||||||
|
keys={[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: 'killed',
|
||||||
|
type: 'user-search',
|
||||||
|
label: 'Getöteter Spieler',
|
||||||
|
options: { required: true, validate: (user) => !!user?.id }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'killer',
|
||||||
|
type: 'user-search',
|
||||||
|
label: 'Killer',
|
||||||
|
options: { validate: (user) => (user?.username ? !!user?.id : true) }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[{ key: 'message', type: 'textarea', label: 'Todesnachricht', options: { required: true, dynamicWidth: true } }]
|
||||||
|
]}
|
||||||
|
onSubmit={editDeath}
|
||||||
|
bind:open={editPopupDeath}
|
||||||
|
/>
|
61
src/app/admin/teamDeaths/teamDeaths.ts
Normal file
61
src/app/admin/teamDeaths/teamDeaths.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { type ActionReturnType, actions } from 'astro:actions';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import { actionErrorPopup } from '@util/action.ts';
|
||||||
|
import { addToWritableArray, deleteFromWritableArray, updateWritableArray } from '@util/state.ts';
|
||||||
|
|
||||||
|
// types
|
||||||
|
export type Deaths = Exclude<ActionReturnType<typeof actions.team.deaths>['data'], undefined>['deaths'];
|
||||||
|
export type Death = Deaths[0];
|
||||||
|
|
||||||
|
// state
|
||||||
|
export const deaths = writable<Deaths>([]);
|
||||||
|
|
||||||
|
// actions
|
||||||
|
export async function fetchDeaths() {
|
||||||
|
const { data, error } = await actions.team.deaths();
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deaths.set(data.deaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addDeath(death: Death) {
|
||||||
|
const { data, error } = await actions.team.addDeath({
|
||||||
|
deadUserId: death.killed.id,
|
||||||
|
killerUserId: death.killer?.id,
|
||||||
|
message: death.message
|
||||||
|
});
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addToWritableArray(deaths, Object.assign(death, { id: data.id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editDeath(death: Death) {
|
||||||
|
const { error } = await actions.team.editDeath({
|
||||||
|
id: death.id,
|
||||||
|
deadUserId: death.killed.id,
|
||||||
|
killerUserId: death.killer?.id,
|
||||||
|
message: death.message
|
||||||
|
});
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWritableArray(deaths, death, (d) => d.id == death.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteDeath(death: Death) {
|
||||||
|
const { error } = await actions.team.deleteDeath({ id: death.id });
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFromWritableArray(deaths, (d) => d.id == death.id);
|
||||||
|
}
|
@ -111,11 +111,11 @@
|
|||||||
submitEnabled = false;
|
submitEnabled = false;
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
for (const k of key) {
|
for (const k of key) {
|
||||||
if (k.options?.required) {
|
|
||||||
if (k.options?.validate) {
|
if (k.options?.validate) {
|
||||||
|
if (k.options?.required && !target[k.key]) {
|
||||||
|
return;
|
||||||
|
} else if (k.options?.required || target[k.key]) {
|
||||||
if (!k.options.validate(target[k.key])) return;
|
if (!k.options.validate(target[k.key])) return;
|
||||||
} else {
|
|
||||||
if (!target[k.key]) return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,9 @@ CREATE TABLE IF NOT EXISTS team_draft (
|
|||||||
|
|
||||||
-- death
|
-- death
|
||||||
CREATE TABLE IF NOT EXISTS death (
|
CREATE TABLE IF NOT EXISTS death (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
message VARCHAR(1024) NOT NULL,
|
message VARCHAR(1024) NOT NULL,
|
||||||
dead_user_id INT NOT NULL,
|
dead_user_id INT NOT NULL UNIQUE,
|
||||||
killer_user_id INT,
|
killer_user_id INT,
|
||||||
FOREIGN KEY (dead_user_id) REFERENCES user(id) ON DELETE CASCADE,
|
FOREIGN KEY (dead_user_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (killer_user_id) REFERENCES user(id) ON DELETE CASCADE
|
FOREIGN KEY (killer_user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
|
@ -83,9 +83,13 @@ import {
|
|||||||
setSettings
|
setSettings
|
||||||
} from './schema/settings';
|
} from './schema/settings';
|
||||||
import {
|
import {
|
||||||
addDeath,
|
|
||||||
type AddDeathReq,
|
type AddDeathReq,
|
||||||
|
addDeath,
|
||||||
death,
|
death,
|
||||||
|
deleteDeath,
|
||||||
|
type DeleteDeathReq,
|
||||||
|
editDeath,
|
||||||
|
type EditDeathReq,
|
||||||
getDeathByUserId,
|
getDeathByUserId,
|
||||||
type GetDeathByUserIdReq,
|
type GetDeathByUserIdReq,
|
||||||
getDeaths,
|
getDeaths,
|
||||||
@ -273,6 +277,8 @@ export class Database {
|
|||||||
|
|
||||||
/* death */
|
/* death */
|
||||||
addDeath = (values: AddDeathReq) => addDeath(this.db, values);
|
addDeath = (values: AddDeathReq) => addDeath(this.db, values);
|
||||||
|
editDeath = (values: EditDeathReq) => editDeath(this.db, values);
|
||||||
|
deleteDeath = (values: DeleteDeathReq) => deleteDeath(this.db, values);
|
||||||
getDeathByUserId = (values: GetDeathByUserIdReq) => getDeathByUserId(this.db, values);
|
getDeathByUserId = (values: GetDeathByUserIdReq) => getDeathByUserId(this.db, values);
|
||||||
getDeaths = (values: GetDeathsReq) => getDeaths(this.db, values);
|
getDeaths = (values: GetDeathsReq) => getDeaths(this.db, values);
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { eq } from 'drizzle-orm';
|
|||||||
type Database = MySql2Database<{ death: typeof death }>;
|
type Database = MySql2Database<{ death: typeof death }>;
|
||||||
|
|
||||||
export const death = mysqlTable('death', {
|
export const death = mysqlTable('death', {
|
||||||
|
id: int('id').primaryKey().autoincrement(),
|
||||||
message: varchar('message', { length: 1024 }).notNull(),
|
message: varchar('message', { length: 1024 }).notNull(),
|
||||||
deadUserId: int('dead_user_id')
|
deadUserId: int('dead_user_id')
|
||||||
.notNull()
|
.notNull()
|
||||||
@ -15,10 +16,21 @@ export const death = mysqlTable('death', {
|
|||||||
|
|
||||||
export type AddDeathReq = {
|
export type AddDeathReq = {
|
||||||
message: string;
|
message: string;
|
||||||
killerUserId?: number;
|
killerUserId?: number | null;
|
||||||
deadUserId: number;
|
deadUserId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EditDeathReq = {
|
||||||
|
id: number;
|
||||||
|
message: string;
|
||||||
|
killerUserId?: number | null;
|
||||||
|
deadUserId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteDeathReq = {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type GetDeathByUserIdReq = {
|
export type GetDeathByUserIdReq = {
|
||||||
userId: number;
|
userId: number;
|
||||||
};
|
};
|
||||||
@ -26,7 +38,24 @@ export type GetDeathByUserIdReq = {
|
|||||||
export type GetDeathsReq = {};
|
export type GetDeathsReq = {};
|
||||||
|
|
||||||
export async function addDeath(db: Database, values: AddDeathReq) {
|
export async function addDeath(db: Database, values: AddDeathReq) {
|
||||||
await db.insert(death).values(values);
|
const ids = await db.insert(death).values(values).$returningId();
|
||||||
|
|
||||||
|
return ids[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editDeath(db: Database, values: EditDeathReq) {
|
||||||
|
await db
|
||||||
|
.update(death)
|
||||||
|
.set({
|
||||||
|
message: values.message,
|
||||||
|
killerUserId: values.killerUserId,
|
||||||
|
deadUserId: values.deadUserId
|
||||||
|
})
|
||||||
|
.where(eq(death.id, values.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteDeath(db: Database, values: DeleteDeathReq) {
|
||||||
|
await db.delete(death).where(eq(death.id, values.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDeathByUserId(db: Database, values: GetDeathByUserIdReq) {
|
export async function getDeathByUserId(db: Database, values: GetDeathByUserIdReq) {
|
||||||
@ -41,6 +70,7 @@ export async function getDeaths(db: Database, _values: GetDeathsReq) {
|
|||||||
|
|
||||||
return db
|
return db
|
||||||
.select({
|
.select({
|
||||||
|
id: death.id,
|
||||||
message: death.message,
|
message: death.message,
|
||||||
killed: {
|
killed: {
|
||||||
id: killed.id,
|
id: killed.id,
|
||||||
|
@ -40,6 +40,13 @@ const adminTabs = [
|
|||||||
href: 'admin/teams',
|
href: 'admin/teams',
|
||||||
name: 'Teams',
|
name: 'Teams',
|
||||||
icon: 'heroicons:users',
|
icon: 'heroicons:users',
|
||||||
|
subTabs: [
|
||||||
|
{
|
||||||
|
href: 'admin/teams/dead',
|
||||||
|
name: 'Tote Spieler',
|
||||||
|
icon: 'heroicons:x-mark'
|
||||||
|
}
|
||||||
|
],
|
||||||
enabled: session?.permissions.users
|
enabled: session?.permissions.users
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
16
src/pages/admin/teams/dead.astro
Normal file
16
src/pages/admin/teams/dead.astro
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
import AdminLayout from '@layouts/admin/AdminLayout.astro';
|
||||||
|
import SidebarActions from '@app/admin/teamDeaths/SidebarActions.svelte';
|
||||||
|
import TeamDeaths from '@app/admin/teamDeaths/TeamDeaths.svelte';
|
||||||
|
import { Session } from '@util/session.ts';
|
||||||
|
import { Permissions } from '@util/permissions.ts';
|
||||||
|
import { BASE_PATH } from 'astro:env/server';
|
||||||
|
|
||||||
|
const session = Session.sessionFromCookies(Astro.cookies, Permissions.Users);
|
||||||
|
if (!session) return Astro.redirect(`${BASE_PATH}/admin`);
|
||||||
|
---
|
||||||
|
|
||||||
|
<AdminLayout title="Tote Spieler">
|
||||||
|
<SidebarActions slot="actions" client:load />
|
||||||
|
<TeamDeaths client:load />
|
||||||
|
</AdminLayout>
|
Reference in New Issue
Block a user