Maerge Commits from release to staging #139

Merged
PxlLoewe merged 25 commits from release into staging 2025-11-08 09:40:40 +00:00
13 changed files with 188 additions and 29 deletions
Showing only changes of commit 6aa6329d83 - Show all commits

View File

@@ -40,7 +40,7 @@ jobs:
username: ${{ secrets.SSH_USERNAME }}
password: ${{ secrets.SSH_PASSWORD }}
port: 22
command_timeout: 30m
command_timeout: 60m
script: |
export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"

View File

@@ -25,7 +25,7 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) =>
const { data: livekitRooms } = useQuery({
queryKey: ["livekit-rooms"],
queryFn: () => getLivekitRooms(),
refetchInterval: 10000,
refetchInterval: 5000,
});
const audioRoom = useAudioStore((s) => s.room?.name);

View File

@@ -234,7 +234,7 @@ const StationTab = ({ aircraft }: { aircraft: ConnectedAircraft & { Station: Sta
const { data: livekitRooms } = useQuery({
queryKey: ["livekit-rooms"],
queryFn: () => getLivekitRooms(),
refetchInterval: 10000,
refetchInterval: 5000,
});
const participants =

View File

@@ -35,7 +35,7 @@ export default function AdminPanel() {
const { data: livekitRooms } = useQuery({
queryKey: ["livekit-rooms"],
queryFn: () => getLivekitRooms(),
refetchInterval: 10000,
refetchInterval: 5000,
});
const kickLivekitParticipantMutation = useMutation({
mutationFn: kickLivekitParticipant,

View File

@@ -17,10 +17,7 @@ export const deleteEvent = async (id: Event["id"]) => {
};
export const upsertAppointment = async (
eventAppointment: Prisma.XOR<
Prisma.EventAppointmentCreateInput,
Prisma.EventAppointmentUncheckedCreateInput
>,
eventAppointment: Prisma.EventAppointmentUncheckedCreateInput,
) => {
const newEventAppointment = eventAppointment.id
? await prisma.eventAppointment.update({

View File

@@ -5,7 +5,6 @@ import { useForm } from "react-hook-form";
import { KEYWORD_CATEGORY, Keyword } from "@repo/db";
import { FileText } from "lucide-react";
import { Input } from "../../../../_components/ui/Input";
import { useState } from "react";
import { deleteKeyword, upsertKeyword } from "../action";
import { Button } from "../../../../_components/ui/Button";
import { redirect } from "next/navigation";
@@ -17,7 +16,6 @@ export const KeywordForm = ({ keyword }: { keyword?: Keyword }) => {
resolver: zodResolver(KeywordOptionalDefaultsSchema),
defaultValues: keyword,
});
const [deleteLoading, setDeleteLoading] = useState(false);
return (
<>
<form
@@ -28,13 +26,13 @@ export const KeywordForm = ({ keyword }: { keyword?: Keyword }) => {
})}
className="grid grid-cols-6 gap-3"
>
<div className="card bg-base-200 shadow-xl col-span-6 ">
<div className="card bg-base-200 col-span-6 shadow-xl">
<div className="card-body">
<h2 className="card-title">
<FileText className="w-5 h-5" /> Allgemeines
<FileText className="h-5 w-5" /> Allgemeines
</h2>
<label className="form-control w-full ">
<span className="label-text text-lg flex items-center gap-2">Kategorie</span>
<label className="form-control w-full">
<span className="label-text flex items-center gap-2 text-lg">Kategorie</span>
<select
className="input-sm select select-bordered select-sm w-full"
{...form.register("category")}
@@ -70,8 +68,8 @@ export const KeywordForm = ({ keyword }: { keyword?: Keyword }) => {
</div>
</div>
<div className="card bg-base-200 shadow-xl col-span-6">
<div className="card-body ">
<div className="card bg-base-200 col-span-6 shadow-xl">
<div className="card-body">
<div className="flex w-full gap-4">
<Button
isLoading={form.formState.isSubmitting}
@@ -83,7 +81,6 @@ export const KeywordForm = ({ keyword }: { keyword?: Keyword }) => {
{keyword && (
<Button
onClick={async () => {
setDeleteLoading(true);
await deleteKeyword(keyword.id);
redirect("/admin/keyword");
}}

View File

@@ -0,0 +1,111 @@
"use client";
import { createReport } from "(app)/admin/report/actions";
import { getUser } from "(app)/admin/user/action";
import { useQuery } from "@tanstack/react-query";
import { Select } from "_components/ui/Select";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { TriangleAlert } from "lucide-react";
import toast from "react-hot-toast";
import { Button } from "@repo/shared-components";
import { ReportOptionalDefaults, ReportOptionalDefaultsSchema } from "@repo/db/zod";
import { zodResolver } from "@hookform/resolvers/zod";
export const NewReportForm = ({
defaultValues,
}: {
defaultValues?: Partial<ReportOptionalDefaults>;
}) => {
const session = useSession();
const [search, setSearch] = useState("");
const { data: user } = useQuery({
queryKey: ["newReport"],
queryFn: async () =>
getUser({
OR: [
{ firstname: { contains: search, mode: "insensitive" } },
{ lastname: { contains: search, mode: "insensitive" } },
{ publicId: { contains: search, mode: "insensitive" } },
{ id: defaultValues?.reportedUserId || "" },
],
}),
enabled: search.length > 0,
refetchOnWindowFocus: false,
});
const router = useRouter();
const form = useForm({
resolver: zodResolver(ReportOptionalDefaultsSchema),
defaultValues: {
reportedUserId: defaultValues?.reportedUserId || "",
senderUserId: session.data?.user.id || "",
},
});
return (
<form
className="flex flex-wrap gap-3"
onSubmit={form.handleSubmit(async (values) => {
console.log("Form submitted with values:", values);
const newReport = await createReport(values);
toast.success("Report erfolgreich erstellt!");
router.push(`/admin/report/${newReport.id}`);
})}
>
<div className="card bg-base-200 flex-1 basis-[800px] shadow-xl">
<div className="card-body">
<h2 className="card-title">
<TriangleAlert /> Neuen Report erstellen
</h2>
<Select
form={form}
options={
user?.map((u) => ({
label: `${u.firstname} ${u.lastname} (${u.publicId})`,
value: u.id,
})) || [
{
label: "Kein Nutzer gefunden",
value: "",
disabled: true,
},
]
}
onInputChange={(v) => setSearch(v)}
name="reportedUserId"
label="Nutzer"
/>
<Select
form={form}
options={[
{ label: "Nutzer", value: "Nutzer" },
{ label: "Pilot", value: "Pilot" },
{ label: "Disponent", value: "Disponent" },
]}
name="reportedUserRole"
label="Rolle des Nutzers"
/>
<textarea
{...form.register("text")}
className="textarea w-full"
placeholder="Beschreibe den Vorfall"
/>
</div>
</div>
<div className="card bg-base-200 flex-1 basis-[800px] shadow-xl">
<div className="card-body">
<div className="flex w-full gap-4">
<Button
isLoading={form.formState.isSubmitting}
type="submit"
className="btn btn-primary flex-1"
>
Speichern
</Button>
</div>
</div>
</div>
</form>
);
};

View File

@@ -26,15 +26,15 @@ export const ReportSenderInfo = ({
return (
<div className="card-body">
<h2 className="card-title">
<Link href={`/admin/user/${Reported?.id}`} className=" link link-hover">
<Link href={`/admin/user/${Reported?.id}`} className="link link-hover">
{Reported?.firstname} {Reported?.lastname} ({Reported?.publicId}) als{" "}
</Link>
<span className="text-primary">{report.reportedUserRole}</span>
</h2>
<div className="textarea w-full text-left">{report.text}</div>
<Link
href={`/admin/user/${Reported?.id}`}
className="text-sm text-gray-600 text-right link link-hover"
href={`/admin/user/${Sender?.id}`}
className="link link-hover text-right text-sm text-gray-600"
>
gemeldet von Nutzer {Sender?.firstname} {Sender?.lastname} ({Sender?.publicId}) am{" "}
{new Date(report.timestamp).toLocaleString()}
@@ -82,7 +82,7 @@ export const ReportAdmin = ({
>
<h2 className="card-title">Staff Kommentar</h2>
<textarea {...form.register("reviewerComment")} className="textarea w-full" placeholder="" />
<p className="text-sm text-gray-600 text-right">
<p className="text-right text-sm text-gray-600">
{report.Reviewer &&
`Kommentar von ${Reviewer?.firstname} ${Reviewer?.lastname} (${Reviewer?.publicId})`}
</p>
@@ -141,7 +141,7 @@ export const ReportPenalties = ({
return (
<div className="card-body">
<h2 className="card-title">
<Shield className="w-5 h-5" /> Strafen zu diesem Report
<Shield className="h-5 w-5" /> Strafen zu diesem Report
</h2>
<PaginatedTable
prismaModel="penalty"

View File

@@ -1,10 +1,7 @@
"use server";
import { Prisma, prisma } from "@repo/db";
export const editReport = async (
id: number,
data: Prisma.ReportUncheckedUpdateInput,
) => {
export const editReport = async (id: number, data: Prisma.ReportUncheckedUpdateInput) => {
return await prisma.report.update({
where: {
id: id,
@@ -12,3 +9,11 @@ export const editReport = async (
data,
});
};
export const createReport = async (
data: Prisma.XOR<Prisma.ReportCreateInput, Prisma.ReportUncheckedCreateInput>,
) => {
return await prisma.report.create({
data,
});
};

View File

@@ -0,0 +1,21 @@
import { NewReportForm } from "(app)/admin/report/_components/NewReport";
const Page = async ({
searchParams,
}: {
searchParams?: {
[key: string]: string | undefined;
};
}) => {
const params = await searchParams;
console.log("searchParams", params);
return (
<NewReportForm
defaultValues={{
reportedUserId: params?.reportedUserId || "",
}}
/>
);
};
export default Page;

View File

@@ -1,6 +1,8 @@
"use client";
import { PaginatedTable } from "_components/PaginatedTable";
import { reportColumns } from "(app)/admin/report/columns";
import { TriangleAlert } from "lucide-react";
import Link from "next/link";
export default function ReportPage() {
return (
@@ -12,6 +14,18 @@ export default function ReportPage() {
Sender: true,
Reported: true,
}}
leftOfSearch={
<p className="flex items-center gap-2 text-left text-2xl font-semibold">
<TriangleAlert className="h-5 w-5" /> Reports
</p>
}
rightOfSearch={
<p className="flex items-center justify-between gap-2 text-left text-2xl font-semibold">
<Link href={"/admin/report/new"}>
<button className="btn btn-sm btn-outline btn-primary">Erstellen</button>
</Link>
</p>
}
columns={reportColumns}
/>
);

View File

@@ -42,6 +42,7 @@ import {
Eye,
LockKeyhole,
PlaneIcon,
Plus,
ShieldUser,
Timer,
Trash2,
@@ -484,9 +485,16 @@ export const UserPenalties = ({ user }: { user: User }) => {
export const UserReports = ({ user }: { user: User }) => {
return (
<div className="card-body">
<h2 className="card-title">
<ExclamationTriangleIcon className="h-5 w-5" /> Nutzer Reports
</h2>
<div className="card-title flex justify-between">
<h2 className="flex items-center gap-2">
<ExclamationTriangleIcon className="h-5 w-5" /> Nutzer Reports
</h2>
<Link href={`/admin/report/new?reportedUserId=${user.id}`}>
<button className={"btn btn-xs btn-square btn-soft cursor-pointer"}>
<Plus />
</button>
</Link>
</div>
<PaginatedTable
prismaModel="report"
filter={{

View File

@@ -3,6 +3,12 @@ import { prisma, Prisma } from "@repo/db";
import bcrypt from "bcryptjs";
import { sendMailByTemplate } from "../../../../helper/mail";
export const getUser = async (where: Prisma.UserWhereInput) => {
return await prisma.user.findMany({
where,
});
};
export const editUser = async (id: string, data: Prisma.UserUpdateInput) => {
return await prisma.user.update({
where: {