add strike reason admin panel
This commit is contained in:
parent
45f984e4da
commit
a21d3d283a
@ -68,6 +68,39 @@ export const report = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
addStrikeReason: defineAction({
|
||||||
|
input: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
weight: z.number()
|
||||||
|
}),
|
||||||
|
handler: async (input, context) => {
|
||||||
|
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
||||||
|
|
||||||
|
return await db.addStrikeReason(input);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
editStrikeReason: defineAction({
|
||||||
|
input: z.object({
|
||||||
|
id: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
weight: z.number()
|
||||||
|
}),
|
||||||
|
handler: async (input, context) => {
|
||||||
|
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
||||||
|
|
||||||
|
await db.editStrikeReason(input);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
deleteStrikeReason: defineAction({
|
||||||
|
input: z.object({
|
||||||
|
id: z.number()
|
||||||
|
}),
|
||||||
|
handler: async (input, context) => {
|
||||||
|
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
||||||
|
|
||||||
|
await db.deleteStrikeReason(input);
|
||||||
|
}
|
||||||
|
}),
|
||||||
strikeReasons: defineAction({
|
strikeReasons: defineAction({
|
||||||
handler: async (_, context) => {
|
handler: async (_, context) => {
|
||||||
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '@iconify/svelte';
|
import Icon from '@iconify/svelte';
|
||||||
import CrudPopup from '@components/admin/popup/CrudPopup.svelte';
|
import CrudPopup from '@components/admin/popup/CrudPopup.svelte';
|
||||||
import { fetchBlockedUsers, addBlockedUser } from '@app/admin/usersBlocked/usersBlocked.ts';
|
import { fetchBlockedUsers, addBlockedUser } from '@app/admin/blockedUsers/blockedUsers.ts';
|
||||||
|
|
||||||
// states
|
// states
|
||||||
let createPopupOpen = $state(false);
|
let createPopupOpen = $state(false);
|
36
src/app/admin/strikeReasons/SidebarActions.svelte
Normal file
36
src/app/admin/strikeReasons/SidebarActions.svelte
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<script>
|
||||||
|
import Icon from '@iconify/svelte';
|
||||||
|
import CrudPopup from '@components/admin/popup/CrudPopup.svelte';
|
||||||
|
import { addStrikeReason, fetchStrikeReasons } from '@app/admin/strikeReasons/strikeReasons.js';
|
||||||
|
|
||||||
|
// states
|
||||||
|
let createPopupOpen = $state(false);
|
||||||
|
|
||||||
|
// lifecycle
|
||||||
|
$effect(() => {
|
||||||
|
fetchStrikeReasons();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-soft w-full" onclick={() => (createPopupOpen = true)}>
|
||||||
|
<Icon icon="heroicons:plus-16-solid" />
|
||||||
|
<span>Neuer Strikegrund</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CrudPopup
|
||||||
|
texts={{
|
||||||
|
title: 'Strikegrund erstellen',
|
||||||
|
submitButtonTitle: 'Erstellen',
|
||||||
|
confirmPopupTitle: 'Strikegrund erstellen?',
|
||||||
|
confirmPopupMessage: 'Soll der Strikegrund erstellt werden?'
|
||||||
|
}}
|
||||||
|
target={null}
|
||||||
|
keys={[
|
||||||
|
[{ key: 'name', type: 'text', label: 'Name', options: { required: true, dynamicWidth: true } }],
|
||||||
|
[{ key: 'weight', type: 'number', label: 'Gewichtung', options: { required: true, dynamicWidth: true } }]
|
||||||
|
]}
|
||||||
|
onSubmit={addStrikeReason}
|
||||||
|
bind:open={createPopupOpen}
|
||||||
|
/>
|
56
src/app/admin/strikeReasons/StrikeReasons.svelte
Normal file
56
src/app/admin/strikeReasons/StrikeReasons.svelte
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import DataTable from '@components/admin/table/DataTable.svelte';
|
||||||
|
import {
|
||||||
|
deleteStrikeReason,
|
||||||
|
editStrikeReason,
|
||||||
|
type StrikeReason,
|
||||||
|
strikeReasons
|
||||||
|
} from '@app/admin/strikeReasons/strikeReasons.ts';
|
||||||
|
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
|
||||||
|
import CrudPopup from '@components/admin/popup/CrudPopup.svelte';
|
||||||
|
|
||||||
|
// state
|
||||||
|
let editPopupStrikeReason = $state(null);
|
||||||
|
let editPopupOpen = $derived(!!editPopupStrikeReason);
|
||||||
|
|
||||||
|
// lifecycle
|
||||||
|
$effect(() => {
|
||||||
|
if (!editPopupOpen) editPopupStrikeReason = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// callback
|
||||||
|
function onBlockedUserDelete(strikeReason: StrikeReason) {
|
||||||
|
$confirmPopupState = {
|
||||||
|
title: 'Nutzer entblockieren?',
|
||||||
|
message: 'Soll der Nutzer wirklich entblockiert werden?\nDieser kann sich danach wieder registrieren.',
|
||||||
|
onConfirm: () => deleteStrikeReason(strikeReason)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
data={strikeReasons}
|
||||||
|
count={true}
|
||||||
|
keys={[
|
||||||
|
{ key: 'name', label: 'Name', width: 20 },
|
||||||
|
{ key: 'weight', label: 'Gewichtung', width: 70, sortable: true }
|
||||||
|
]}
|
||||||
|
onDelete={onBlockedUserDelete}
|
||||||
|
onEdit={(strikeReason) => (editPopupStrikeReason = strikeReason)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CrudPopup
|
||||||
|
texts={{
|
||||||
|
title: 'Strikegrund bearbeiten',
|
||||||
|
submitButtonTitle: 'Speichern',
|
||||||
|
confirmPopupTitle: 'Änderungen speichern',
|
||||||
|
confirmPopupMessage: 'Sollen die Änderungen gespeichert werden?'
|
||||||
|
}}
|
||||||
|
target={editPopupStrikeReason}
|
||||||
|
keys={[
|
||||||
|
[{ key: 'name', type: 'text', label: 'Name', options: { required: true, dynamicWidth: true } }],
|
||||||
|
[{ key: 'weight', type: 'number', label: 'Gewichtung', options: { required: true, dynamicWidth: true } }]
|
||||||
|
]}
|
||||||
|
onSubmit={editStrikeReason}
|
||||||
|
bind:open={editPopupOpen}
|
||||||
|
/>
|
55
src/app/admin/strikeReasons/strikeReasons.ts
Normal file
55
src/app/admin/strikeReasons/strikeReasons.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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 StrikeReasons = Exclude<
|
||||||
|
ActionReturnType<typeof actions.report.strikeReasons>['data'],
|
||||||
|
undefined
|
||||||
|
>['strikeReasons'];
|
||||||
|
export type StrikeReason = StrikeReasons[0];
|
||||||
|
|
||||||
|
// state
|
||||||
|
export const strikeReasons = writable<StrikeReasons>([]);
|
||||||
|
|
||||||
|
// actions
|
||||||
|
export async function fetchStrikeReasons() {
|
||||||
|
const { data, error } = await actions.report.strikeReasons();
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
strikeReasons.set(data.strikeReasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addStrikeReason(strikeReason: StrikeReason) {
|
||||||
|
const { data, error } = await actions.report.addStrikeReason(strikeReason);
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addToWritableArray(strikeReasons, Object.assign(strikeReason, { id: data.id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editStrikeReason(strikeReason: StrikeReason) {
|
||||||
|
const { error } = await actions.report.editStrikeReason(strikeReason);
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWritableArray(strikeReasons, strikeReason, (t) => t.id == strikeReason.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteStrikeReason(strikeReason: StrikeReason) {
|
||||||
|
const { error } = await actions.report.deleteStrikeReason(strikeReason);
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFromWritableArray(strikeReasons, (t) => t.id == strikeReason.id);
|
||||||
|
}
|
@ -42,6 +42,7 @@
|
|||||||
| 'color'
|
| 'color'
|
||||||
| 'date'
|
| 'date'
|
||||||
| 'datetime-local'
|
| 'datetime-local'
|
||||||
|
| 'number'
|
||||||
| 'password'
|
| 'password'
|
||||||
| 'tel'
|
| 'tel'
|
||||||
| 'team-search'
|
| 'team-search'
|
||||||
@ -56,6 +57,7 @@
|
|||||||
['color']: {};
|
['color']: {};
|
||||||
['date']: {};
|
['date']: {};
|
||||||
['datetime-local']: {};
|
['datetime-local']: {};
|
||||||
|
['number']: {};
|
||||||
['password']: {};
|
['password']: {};
|
||||||
['tel']: {};
|
['tel']: {};
|
||||||
['team-search']: {};
|
['team-search']: {};
|
||||||
@ -168,7 +170,7 @@
|
|||||||
class:grid-cols-2={key.length === 2}
|
class:grid-cols-2={key.length === 2}
|
||||||
>
|
>
|
||||||
{#each key as k (k)}
|
{#each key as k (k)}
|
||||||
{#if k.type === 'color' || k.type === 'date' || k.type === 'datetime-local' || k.type === 'tel' || k.type === 'text'}
|
{#if k.type === 'color' || k.type === 'date' || k.type === 'datetime-local' || k.type === 'number' || k.type === 'tel' || k.type === 'text'}
|
||||||
<Input
|
<Input
|
||||||
type={k.type}
|
type={k.type}
|
||||||
bind:value={() => target[k.key] ?? k.default, (v) => onBindChange(k.key, v, k.options)}
|
bind:value={() => target[k.key] ?? k.default, (v) => onBindChange(k.key, v, k.options)}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id?: string;
|
id?: string;
|
||||||
type?: 'color' | 'date' | 'datetime-local' | 'tel' | 'text' | 'email';
|
type?: 'color' | 'date' | 'datetime-local' | 'number' | 'tel' | 'text' | 'email';
|
||||||
value?: string | null;
|
value?: string | null;
|
||||||
label?: string;
|
label?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
|
@ -98,7 +98,17 @@ import {
|
|||||||
} from './schema/feedback.ts';
|
} from './schema/feedback.ts';
|
||||||
import { addReport, type AddReportReq, getReports, type GetReportsReq, report } from './schema/report.ts';
|
import { addReport, type AddReportReq, getReports, type GetReportsReq, report } from './schema/report.ts';
|
||||||
import { DATABASE_URI } from 'astro:env/server';
|
import { DATABASE_URI } from 'astro:env/server';
|
||||||
import { type GetStrikeReasonsReq, getStrikeReasons, strikeReason } from '@db/schema/strikeReason.ts';
|
import {
|
||||||
|
type GetStrikeReasonsReq,
|
||||||
|
getStrikeReasons,
|
||||||
|
strikeReason,
|
||||||
|
type AddStrikeReasonReq,
|
||||||
|
addStrikeReason,
|
||||||
|
type EditStrikeReasonReq,
|
||||||
|
editStrikeReason,
|
||||||
|
type DeleteStrikeReasonReq,
|
||||||
|
deleteStrikeReason
|
||||||
|
} from '@db/schema/strikeReason.ts';
|
||||||
import { getStrikesByTeamId, type GetStrikesByTeamId, strike } from '@db/schema/strike.ts';
|
import { getStrikesByTeamId, type GetStrikesByTeamId, strike } from '@db/schema/strike.ts';
|
||||||
import {
|
import {
|
||||||
editReportStatus,
|
editReportStatus,
|
||||||
@ -232,7 +242,12 @@ export class Database {
|
|||||||
editReportStatus = (values: EditReportStatusReq) => editReportStatus(this.db, values);
|
editReportStatus = (values: EditReportStatusReq) => editReportStatus(this.db, values);
|
||||||
|
|
||||||
/* strike reason */
|
/* strike reason */
|
||||||
|
addStrikeReason = (values: AddStrikeReasonReq) => addStrikeReason(this.db, values);
|
||||||
|
editStrikeReason = (values: EditStrikeReasonReq) => editStrikeReason(this.db, values);
|
||||||
|
deleteStrikeReason = (values: DeleteStrikeReasonReq) => deleteStrikeReason(this.db, values);
|
||||||
getStrikeReasons = (values: GetStrikeReasonsReq) => getStrikeReasons(this.db, values);
|
getStrikeReasons = (values: GetStrikeReasonsReq) => getStrikeReasons(this.db, values);
|
||||||
|
|
||||||
|
/* strikes */
|
||||||
getStrikesByTeamId = (values: GetStrikesByTeamId) => getStrikesByTeamId(this.db, values);
|
getStrikesByTeamId = (values: GetStrikesByTeamId) => getStrikesByTeamId(this.db, values);
|
||||||
|
|
||||||
/* feedback */
|
/* feedback */
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { int, mysqlTable, tinyint, varchar } from 'drizzle-orm/mysql-core';
|
import { int, mysqlTable, tinyint, varchar } from 'drizzle-orm/mysql-core';
|
||||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||||
import { asc } from 'drizzle-orm';
|
import { asc, eq } from 'drizzle-orm';
|
||||||
|
|
||||||
type Database = MySql2Database<{ strikeReason: typeof strikeReason }>;
|
type Database = MySql2Database<{ strikeReason: typeof strikeReason }>;
|
||||||
|
|
||||||
@ -10,8 +10,33 @@ export const strikeReason = mysqlTable('strike_reason', {
|
|||||||
weight: tinyint('weight').notNull()
|
weight: tinyint('weight').notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type AddStrikeReasonReq = {
|
||||||
|
name: string;
|
||||||
|
weight: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EditStrikeReasonReq = typeof strikeReason.$inferSelect;
|
||||||
|
|
||||||
|
export type DeleteStrikeReasonReq = {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type GetStrikeReasonsReq = {};
|
export type GetStrikeReasonsReq = {};
|
||||||
|
|
||||||
|
export async function addStrikeReason(db: Database, values: AddStrikeReasonReq) {
|
||||||
|
const sr = await db.insert(strikeReason).values(values).$returningId();
|
||||||
|
|
||||||
|
return sr[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editStrikeReason(db: Database, values: EditStrikeReasonReq) {
|
||||||
|
await db.update(strikeReason).set(values).where(eq(strikeReason.id, values.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteStrikeReason(db: Database, values: DeleteStrikeReasonReq) {
|
||||||
|
await db.delete(strikeReason).where(eq(strikeReason.id, values.id));
|
||||||
|
}
|
||||||
|
|
||||||
export async function getStrikeReasons(db: Database, _values: GetStrikeReasonsReq) {
|
export async function getStrikeReasons(db: Database, _values: GetStrikeReasonsReq) {
|
||||||
return db.select().from(strikeReason).orderBy(asc(strikeReason.weight));
|
return db.select().from(strikeReason).orderBy(asc(strikeReason.weight));
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,13 @@ const adminTabs = [
|
|||||||
href: 'admin/reports',
|
href: 'admin/reports',
|
||||||
name: 'Reports',
|
name: 'Reports',
|
||||||
icon: 'heroicons:flag',
|
icon: 'heroicons:flag',
|
||||||
|
subTabs: [
|
||||||
|
{
|
||||||
|
href: 'admin/reports/reasons',
|
||||||
|
name: 'Strikegründe',
|
||||||
|
icon: 'heroicons:shield-exclamation'
|
||||||
|
}
|
||||||
|
],
|
||||||
enabled: session?.permissions.reports
|
enabled: session?.permissions.reports
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
16
src/pages/admin/reports/reasons.astro
Normal file
16
src/pages/admin/reports/reasons.astro
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
import { Session } from '@util/session';
|
||||||
|
import { Permissions } from '@util/permissions';
|
||||||
|
import { BASE_PATH } from 'astro:env/server';
|
||||||
|
import AdminLayout from '@layouts/admin/AdminLayout.astro';
|
||||||
|
import SidebarActions from '@app/admin/strikeReasons/SidebarActions.svelte';
|
||||||
|
import StrikeReasons from '@app/admin/strikeReasons/StrikeReasons.svelte';
|
||||||
|
|
||||||
|
const session = Session.sessionFromCookies(Astro.cookies, Permissions.Reports);
|
||||||
|
if (!session) return Astro.redirect(`${BASE_PATH}/admin`);
|
||||||
|
---
|
||||||
|
|
||||||
|
<AdminLayout title="Reports">
|
||||||
|
<SidebarActions slot="actions" client:load />
|
||||||
|
<StrikeReasons client:load />
|
||||||
|
</AdminLayout>
|
Loading…
x
Reference in New Issue
Block a user