Added time Ban and penalty

This commit is contained in:
PxlLoewe
2025-06-19 11:48:45 -07:00
parent e40cf0ffac
commit 4732ecb770
15 changed files with 327 additions and 56 deletions

View File

@@ -2,13 +2,25 @@
import { useEffect } from "react";
export const Error = ({ statusCode, title }: { statusCode: number; title: string }) => {
export const Error = ({
statusCode,
title,
description,
}: {
statusCode: number;
title: string;
description?: string;
}) => {
return (
<div className="flex-1 flex items-center justify-center h-full">
<div className="rounded-2xl bg-base-300 p-8 text-center max-w-md w-full">
<h1 className="text-6xl font-bold text-red-500">{statusCode}</h1>
<p className="text-xl font-semibold mt-4">Oh nein! Ein Fehler ist aufgetreten.</p>
<p className="text-gray-600 mt-2">{title || "Ein unerwarteter Fehler ist aufgetreten."}</p>
<p className="text-xl font-semibold mt-4">
{title ? title : "Oh nein! Ein Fehler ist aufgetreten."}
</p>
<p className="text-gray-600 mt-2">
{description || "Ein unerwarteter Fehler ist aufgetreten."}
</p>
<button onClick={() => window.location.reload()} className="btn btn-dash my-2">
Refresh Page
</button>

View File

@@ -19,10 +19,9 @@ export const AdminMessageToast = ({ event, t }: { event: AdminMessage; t: Toast
event.status == "kick" && "text-yellow-500 ",
)}
>
Du wurdes durch den Admin {event.data?.admin.publicId}{" "}
{event.status == "ban" ? "gebannt" : "gekickt"}!
{event.message}
</h1>
<p>{event.message}</p>
<p>{event.data?.reason}</p>
</div>
<div className="ml-11">
<button className="btn" onClick={handleClick}>

View File

@@ -1,6 +1,7 @@
"use client";
import { PublicUser } from "@repo/db";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { cn } from "_helpers/cn";
import { getConnectedAircraftsAPI, kickAircraftAPI } from "_querys/aircrafts";
import { getConnectedDispatcherAPI, kickDispatcherAPI } from "_querys/dispatcher";
import { getLivekitRooms, kickLivekitParticipant } from "_querys/livekit";
@@ -18,9 +19,115 @@ import {
UserCheck,
Workflow,
} from "lucide-react";
import { useRef } from "react";
import { ReactNode, useRef, useState } from "react";
import toast from "react-hot-toast";
const PenaltyDropdown = ({
onClick,
btnClassName,
showDatePicker,
btnTip,
Icon,
}: {
onClick: (data: { reason: string; until: Date | null }) => void;
showDatePicker?: boolean;
btnClassName?: string;
btnTip?: string;
Icon: ReactNode;
}) => {
const [reason, setReason] = useState("");
const [until, setUntil] = useState<string>("default");
return (
<details className="dropdown dropdown-left dropdown-center">
<summary className={cn("btn btn-xs btn-square btn-soft", btnClassName)}>{Icon}</summary>
<div className="dropdown-content flex gap-3 bg-base-100 rounded-box z-1 p-2 mr-3 shadow-sm">
<input
value={reason}
onChange={(e) => setReason(e.target.value)}
type="text"
className="input min-w-[250px]"
placeholder="Begründung"
/>
{showDatePicker && (
<select
className="select min-w-[150px] select-bordered"
value={until}
onChange={(e) => setUntil(e.target.value)}
>
<option value="default" disabled>
Unbegrenzt
</option>
<option value="1h">1 Stunde</option>
<option value="6h">6 Stunden</option>
<option value="12h">12 Stunden</option>
<option value="24h">24 Stunden</option>
<option value="72h">72 Stunden</option>
<option value="1w">1 Woche</option>
<option value="2w">2 Wochen</option>
<option value="1m">1 Monat</option>
<option value="3m">3 Monate</option>
<option value="6m">6 Monate</option>
<option value="1y">1 Jahr</option>
</select>
)}
<button
className={cn(
"btn btn-square btn-soft tooltip tooltip-bottom tooltip-warning",
btnClassName,
)}
data-tip={btnTip}
onClick={() => {
let untilDate: Date | null = null;
if (until !== "default") {
const now = new Date();
switch (until) {
case "1h":
untilDate = new Date(now.getTime() + 1 * 60 * 60 * 1000);
break;
case "6h":
untilDate = new Date(now.getTime() + 6 * 60 * 60 * 1000);
break;
case "12h":
untilDate = new Date(now.getTime() + 12 * 60 * 60 * 1000);
break;
case "24h":
untilDate = new Date(now.getTime() + 24 * 60 * 60 * 1000);
break;
case "72h":
untilDate = new Date(now.getTime() + 72 * 60 * 60 * 1000);
break;
case "1w":
untilDate = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
break;
case "2w":
untilDate = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000);
break;
case "1m":
untilDate = new Date(now.setMonth(now.getMonth() + 1));
break;
case "3m":
untilDate = new Date(now.setMonth(now.getMonth() + 3));
break;
case "6m":
untilDate = new Date(now.setMonth(now.getMonth() + 6));
break;
case "1y":
untilDate = new Date(now.setFullYear(now.getFullYear() + 1));
break;
default:
untilDate = null;
}
}
onClick({ reason, until: untilDate });
}}
>
{Icon}
</button>
</div>
</details>
);
};
export default function AdminPanel() {
const queryClient = useQueryClient();
const { data: pilots } = useQuery({
@@ -45,9 +152,6 @@ export default function AdminPanel() {
queryClient.invalidateQueries({ queryKey: ["livekit-rooms"] });
},
});
const editUSerMutation = useMutation({
mutationFn: editUserAPI,
});
const kickPilotMutation = useMutation({
mutationFn: kickAircraftAPI,
onSuccess: () => {
@@ -153,22 +257,23 @@ export default function AdminPanel() {
)}
</td>
<td className="flex gap-2">
<button
className="btn btn-xs btn-square btn-warning btn-soft tooltip tooltip-bottom tooltip-warning"
data-tip="Kick"
onClick={() => kickPilotMutation.mutate({ id: p.id })}
>
<RedoDot size={15} />
</button>
<button
className="btn btn-xs btn-square btn-error btn-soft tooltip tooltip-bottom tooltip-error"
data-tip="Ban"
onClick={() => {
kickPilotMutation.mutate({ id: p.id, bann: true });
}}
>
<LockKeyhole size={15} />
</button>
<PenaltyDropdown
btnClassName="btn-warning"
btnTip="Kick"
Icon={<RedoDot size={15} />}
onClick={({ reason }) =>
kickPilotMutation.mutate({ id: p.id, reason })
}
/>
<PenaltyDropdown
btnClassName="btn-error"
btnTip="Ban"
showDatePicker
Icon={<LockKeyhole size={15} />}
onClick={({ reason, until }) =>
kickPilotMutation.mutate({ id: p.id, reason, bann: true, until })
}
/>
<a
href={`${process.env.NEXT_PUBLIC_HUB_URL}/admin/user/${p.userId}`}
target="_blank"
@@ -205,22 +310,23 @@ export default function AdminPanel() {
)}
</td>
<td className="flex gap-2">
<button
className="btn btn-xs btn-square btn-warning btn-soft tooltip tooltip-bottom tooltip-warning"
data-tip="Kick"
onClick={() => kickDispatchMutation.mutate({ id: d.id })}
>
<RedoDot size={15} />
</button>
<button
className="btn btn-xs btn-square btn-error btn-soft tooltip tooltip-bottom tooltip-error"
data-tip="Ban"
onClick={() => {
kickDispatchMutation.mutate({ id: d.id, bann: true });
}}
>
<LockKeyhole size={15} />
</button>
<PenaltyDropdown
btnClassName="btn-warning"
btnTip="Kick"
Icon={<RedoDot size={15} />}
onClick={({ reason }) =>
kickDispatchMutation.mutate({ id: d.id, reason })
}
/>
<PenaltyDropdown
btnClassName="btn-error"
btnTip="Ban"
showDatePicker
Icon={<LockKeyhole size={15} />}
onClick={({ reason, until }) =>
kickDispatchMutation.mutate({ id: d.id, reason, bann: true, until })
}
/>
<a
href={`${process.env.NEXT_PUBLIC_HUB_URL}/admin/user/${d.userId}`}
target="_blank"

View File

@@ -29,9 +29,19 @@ export const getConnectedAircraftPositionLogAPI = async ({ id }: { id: number })
return res.data;
};
export const kickAircraftAPI = async ({ id, bann }: { id: number; bann?: boolean }) => {
export const kickAircraftAPI = async ({
id,
bann,
reason,
until = null,
}: {
id: number;
bann?: boolean;
reason: string;
until?: Date | null;
}) => {
const res = await serverApi.delete(`/aircrafts/${id}`, {
data: { bann },
data: { bann, reason, until },
});
console.log(res.status);
if (res.status != 204) {

View File

@@ -25,9 +25,19 @@ export const getConnectedDispatcherAPI = async (filter?: Prisma.ConnectedDispatc
return res.data;
};
export const kickDispatcherAPI = async ({ id, bann }: { id: number; bann?: boolean }) => {
export const kickDispatcherAPI = async ({
id,
bann,
reason,
until = null,
}: {
id: number;
bann?: boolean;
reason: string;
until?: Date | null;
}) => {
const res = await serverApi.delete(`/dispatcher/${id}`, {
data: { bann },
data: { bann, reason, until },
});
console.log(res.status);
if (res.status != 204) {

View File

@@ -3,6 +3,7 @@ import Navbar from "./_components/navbar/Navbar";
import { redirect } from "next/navigation";
import { getServerSession } from "../api/auth/[...nextauth]/auth";
import { Error } from "_components/Error";
import { prisma } from "@repo/db";
export const metadata: Metadata = {
title: "VAR: Disponent",
@@ -15,6 +16,15 @@ export default async function RootLayout({
children: React.ReactNode;
}>) {
const session = await getServerSession();
const openPenaltys = await prisma.penalty.findMany({
where: {
userId: session?.user.id,
until: {
gte: new Date(),
},
type: "TIME_BAN",
},
});
if (!session || !session.user) {
redirect("/login");
@@ -25,6 +35,17 @@ export default async function RootLayout({
if (!session.user.permissions.includes("DISPO"))
return <Error title="Zugriff verweigert" statusCode={403} />;
if (openPenaltys[0]) {
return (
<Error
title="Du hast eine aktive Strafe"
statusCode={403}
description={`Du bist bis zum ${new Date(openPenaltys[0].until!).toLocaleString()} gesperrt.`}
/>
);
}
return (
<>
<Navbar />

View File

@@ -3,6 +3,7 @@ import Navbar from "./_components/navbar/Navbar";
import { redirect } from "next/navigation";
import { getServerSession } from "../api/auth/[...nextauth]/auth";
import { Error } from "_components/Error";
import { prisma } from "@repo/db";
export const metadata: Metadata = {
title: "VAR: Pilot",
@@ -15,6 +16,15 @@ export default async function RootLayout({
children: React.ReactNode;
}>) {
const session = await getServerSession();
const openPenaltys = await prisma.penalty.findMany({
where: {
userId: session?.user.id,
until: {
gte: new Date(),
},
type: "TIME_BAN",
},
});
if (!session || !session.user.firstname) {
redirect("/login");
@@ -25,6 +35,17 @@ export default async function RootLayout({
if (!session.user.permissions.includes("PILOT"))
return <Error title="Zugriff verweigert" statusCode={403} />;
if (openPenaltys[0]) {
return (
<Error
title="Du hast eine aktive Strafe"
statusCode={403}
description={`Du bist bis zum ${new Date(openPenaltys[0].until!).toLocaleString()} gesperrt.`}
/>
);
}
return (
<>
<Navbar />