diff --git a/src/actions/report.ts b/src/actions/report.ts index 52c5288..9dfc2c4 100644 --- a/src/actions/report.ts +++ b/src/actions/report.ts @@ -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({ handler: async (_, context) => { Session.actionSessionFromCookies(context.cookies, Permissions.Reports); diff --git a/src/app/admin/usersBlocked/SidebarActions.svelte b/src/app/admin/blockedUsers/SidebarActions.svelte similarity index 97% rename from src/app/admin/usersBlocked/SidebarActions.svelte rename to src/app/admin/blockedUsers/SidebarActions.svelte index ccebe61..113239e 100644 --- a/src/app/admin/usersBlocked/SidebarActions.svelte +++ b/src/app/admin/blockedUsers/SidebarActions.svelte @@ -1,7 +1,7 @@ + +
+ +
+ + diff --git a/src/app/admin/strikeReasons/StrikeReasons.svelte b/src/app/admin/strikeReasons/StrikeReasons.svelte new file mode 100644 index 0000000..903e589 --- /dev/null +++ b/src/app/admin/strikeReasons/StrikeReasons.svelte @@ -0,0 +1,56 @@ + + + (editPopupStrikeReason = strikeReason)} +/> + + diff --git a/src/app/admin/strikeReasons/strikeReasons.ts b/src/app/admin/strikeReasons/strikeReasons.ts new file mode 100644 index 0000000..228e39f --- /dev/null +++ b/src/app/admin/strikeReasons/strikeReasons.ts @@ -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['data'], + undefined +>['strikeReasons']; +export type StrikeReason = StrikeReasons[0]; + +// state +export const strikeReasons = writable([]); + +// 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); +} diff --git a/src/components/admin/popup/CrudPopup.svelte b/src/components/admin/popup/CrudPopup.svelte index dcf8144..79d8e40 100644 --- a/src/components/admin/popup/CrudPopup.svelte +++ b/src/components/admin/popup/CrudPopup.svelte @@ -42,6 +42,7 @@ | 'color' | 'date' | 'datetime-local' + | 'number' | 'password' | 'tel' | 'team-search' @@ -56,6 +57,7 @@ ['color']: {}; ['date']: {}; ['datetime-local']: {}; + ['number']: {}; ['password']: {}; ['tel']: {}; ['team-search']: {}; @@ -168,7 +170,7 @@ class:grid-cols-2={key.length === 2} > {#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'} target[k.key] ?? k.default, (v) => onBindChange(k.key, v, k.options)} diff --git a/src/components/input/Input.svelte b/src/components/input/Input.svelte index 6c1a411..29e83ec 100644 --- a/src/components/input/Input.svelte +++ b/src/components/input/Input.svelte @@ -3,7 +3,7 @@ interface Props { id?: string; - type?: 'color' | 'date' | 'datetime-local' | 'tel' | 'text' | 'email'; + type?: 'color' | 'date' | 'datetime-local' | 'number' | 'tel' | 'text' | 'email'; value?: string | null; label?: string; required?: boolean; diff --git a/src/db/database.ts b/src/db/database.ts index 7f01769..7c5915d 100644 --- a/src/db/database.ts +++ b/src/db/database.ts @@ -98,7 +98,17 @@ import { } from './schema/feedback.ts'; import { addReport, type AddReportReq, getReports, type GetReportsReq, report } from './schema/report.ts'; 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 { editReportStatus, @@ -232,7 +242,12 @@ export class Database { editReportStatus = (values: EditReportStatusReq) => editReportStatus(this.db, values); /* 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); + + /* strikes */ getStrikesByTeamId = (values: GetStrikesByTeamId) => getStrikesByTeamId(this.db, values); /* feedback */ diff --git a/src/db/schema/strikeReason.ts b/src/db/schema/strikeReason.ts index 5e17444..0d10492 100644 --- a/src/db/schema/strikeReason.ts +++ b/src/db/schema/strikeReason.ts @@ -1,6 +1,6 @@ import { int, mysqlTable, tinyint, varchar } from 'drizzle-orm/mysql-core'; import type { MySql2Database } from 'drizzle-orm/mysql2'; -import { asc } from 'drizzle-orm'; +import { asc, eq } from 'drizzle-orm'; type Database = MySql2Database<{ strikeReason: typeof strikeReason }>; @@ -10,8 +10,33 @@ export const strikeReason = mysqlTable('strike_reason', { 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 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) { return db.select().from(strikeReason).orderBy(asc(strikeReason.weight)); } diff --git a/src/layouts/admin/AdminLayout.astro b/src/layouts/admin/AdminLayout.astro index af7d3e1..eb94787 100644 --- a/src/layouts/admin/AdminLayout.astro +++ b/src/layouts/admin/AdminLayout.astro @@ -46,6 +46,13 @@ const adminTabs = [ href: 'admin/reports', name: 'Reports', icon: 'heroicons:flag', + subTabs: [ + { + href: 'admin/reports/reasons', + name: 'Strikegründe', + icon: 'heroicons:shield-exclamation' + } + ], enabled: session?.permissions.reports }, { diff --git a/src/pages/admin/reports/reasons.astro b/src/pages/admin/reports/reasons.astro new file mode 100644 index 0000000..43d630e --- /dev/null +++ b/src/pages/admin/reports/reasons.astro @@ -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`); +--- + + + + +