Merge pull request #133 from VAR-Virtual-Air-Rescue/staging
v2.0.3
This commit was merged in pull request #133.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { getPublicUser, prisma, User } from "@repo/db";
|
import { getPublicUser, prisma, User } from "@repo/db";
|
||||||
import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord";
|
import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord";
|
||||||
import { getNextDateWithTime } from "@repo/shared-components";
|
import { getNextDateWithTime, getUserPenaltys } from "@repo/shared-components";
|
||||||
import { DISCORD_ROLES } from "@repo/db";
|
import { DISCORD_ROLES } from "@repo/db";
|
||||||
import { Server, Socket } from "socket.io";
|
import { Server, Socket } from "socket.io";
|
||||||
|
|
||||||
@@ -28,8 +28,17 @@ export const handleConnectDispatch =
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.permissions?.includes("DISPO")) {
|
const userPenaltys = await getUserPenaltys(user.id);
|
||||||
socket.emit("error", "You do not have permission to connect to the dispatch server.");
|
|
||||||
|
if (
|
||||||
|
userPenaltys.openTimeban.length > 0 ||
|
||||||
|
user.isBanned ||
|
||||||
|
userPenaltys.openBans.length > 0
|
||||||
|
) {
|
||||||
|
socket.emit("connect-message", {
|
||||||
|
message: "Du hast eine aktive Strafe und kannst dich deshalb nicht verbinden.",
|
||||||
|
});
|
||||||
|
socket.disconnect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { getPublicUser, prisma, User } from "@repo/db";
|
import { getPublicUser, prisma, User } from "@repo/db";
|
||||||
import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord";
|
import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord";
|
||||||
import { getNextDateWithTime } from "@repo/shared-components";
|
|
||||||
import { DISCORD_ROLES } from "@repo/db";
|
import { DISCORD_ROLES } from "@repo/db";
|
||||||
import { Server, Socket } from "socket.io";
|
import { Server, Socket } from "socket.io";
|
||||||
|
import { getUserPenaltys } from "@repo/shared-components";
|
||||||
|
|
||||||
export const handleConnectPilot =
|
export const handleConnectPilot =
|
||||||
(socket: Socket, io: Server) =>
|
(socket: Socket, io: Server) =>
|
||||||
@@ -34,6 +34,19 @@ export const handleConnectPilot =
|
|||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const userPenaltys = await getUserPenaltys(userId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
userPenaltys.openTimeban.length > 0 ||
|
||||||
|
user.isBanned ||
|
||||||
|
userPenaltys.openBans.length > 0
|
||||||
|
) {
|
||||||
|
socket.emit("connect-message", {
|
||||||
|
message: "Du hast eine aktive Strafe und kannst dich deshalb nicht verbinden.",
|
||||||
|
});
|
||||||
|
socket.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) return Error("User not found");
|
if (!user) return Error("User not found");
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Server, Socket } from "socket.io";
|
|||||||
export const handleSendMessage =
|
export const handleSendMessage =
|
||||||
(socket: Socket, io: Server) =>
|
(socket: Socket, io: Server) =>
|
||||||
async (
|
async (
|
||||||
{ userId, message }: { userId: string; message: string },
|
{ userId, message, role }: { userId: string; message: string; role: string },
|
||||||
cb: (err: { error?: string }) => void,
|
cb: (err: { error?: string }) => void,
|
||||||
) => {
|
) => {
|
||||||
const senderId = socket.data.user.id;
|
const senderId = socket.data.user.id;
|
||||||
@@ -24,7 +24,7 @@ export const handleSendMessage =
|
|||||||
receiverId: userId,
|
receiverId: userId,
|
||||||
senderId,
|
senderId,
|
||||||
receiverName: `${receiverUser?.firstname} ${receiverUser?.lastname[0]}. - ${receiverUser?.publicId}`,
|
receiverName: `${receiverUser?.firstname} ${receiverUser?.lastname[0]}. - ${receiverUser?.publicId}`,
|
||||||
senderName: `${senderUser?.firstname} ${senderUser?.lastname[0]}. - ${senderUser?.publicId}`,
|
senderName: `${senderUser?.firstname} ${senderUser?.lastname[0]}. - ${role ?? senderUser?.publicId}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { getConnectedAircraftPositionLogAPI, getConnectedAircraftsAPI } from "_q
|
|||||||
import { getMissionsAPI } from "_querys/missions";
|
import { getMissionsAPI } from "_querys/missions";
|
||||||
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
||||||
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||||
import { useSession } from "next-auth/react";
|
|
||||||
|
|
||||||
const AircraftPopupContent = ({
|
const AircraftPopupContent = ({
|
||||||
aircraft,
|
aircraft,
|
||||||
@@ -73,7 +72,7 @@ const AircraftPopupContent = ({
|
|||||||
}
|
}
|
||||||
}, [currentTab, aircraft, mission]);
|
}, [currentTab, aircraft, mission]);
|
||||||
|
|
||||||
const { setOpenAircraftMarker, setMap, openAircraftMarker } = useMapStore((state) => state);
|
const { setOpenAircraftMarker, setMap } = useMapStore((state) => state);
|
||||||
const { anchor } = useSmartPopup();
|
const { anchor } = useSmartPopup();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -435,6 +434,9 @@ export const AircraftLayer = () => {
|
|||||||
}
|
}
|
||||||
}, [pilotConnectionStatus, followOwnAircraft, ownAircraft, setMap, map]);
|
}, [pilotConnectionStatus, followOwnAircraft, ownAircraft, setMap, map]);
|
||||||
|
|
||||||
|
console.debug("Hubschrauber auf Karte:", filteredAircrafts.length, filteredAircrafts);
|
||||||
|
console.debug("Daten vom Server:", aircrafts?.length, aircrafts);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{filteredAircrafts?.map((aircraft) => {
|
{filteredAircrafts?.map((aircraft) => {
|
||||||
|
|||||||
@@ -92,11 +92,6 @@ export default function AdminPanel() {
|
|||||||
|
|
||||||
const modalRef = useRef<HTMLDialogElement>(null);
|
const modalRef = useRef<HTMLDialogElement>(null);
|
||||||
|
|
||||||
console.debug("piloten von API", {
|
|
||||||
anzahl: pilots?.length,
|
|
||||||
pilots,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { create } from "zustand";
|
|||||||
import { ChatMessage } from "@repo/db";
|
import { ChatMessage } from "@repo/db";
|
||||||
import { dispatchSocket } from "(app)/dispatch/socket";
|
import { dispatchSocket } from "(app)/dispatch/socket";
|
||||||
import { pilotSocket } from "(app)/pilot/socket";
|
import { pilotSocket } from "(app)/pilot/socket";
|
||||||
|
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
||||||
|
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||||
|
|
||||||
interface ChatStore {
|
interface ChatStore {
|
||||||
situationTabOpen: boolean;
|
situationTabOpen: boolean;
|
||||||
@@ -16,7 +18,12 @@ interface ChatStore {
|
|||||||
setOwnId: (id: string) => void;
|
setOwnId: (id: string) => void;
|
||||||
chats: Record<string, { name: string; notification: boolean; messages: ChatMessage[] }>;
|
chats: Record<string, { name: string; notification: boolean; messages: ChatMessage[] }>;
|
||||||
setChatNotification: (userId: string, notification: boolean) => void;
|
setChatNotification: (userId: string, notification: boolean) => void;
|
||||||
sendMessage: (userId: string, message: string) => Promise<void>;
|
sendMessage: (
|
||||||
|
userId: string,
|
||||||
|
message: string,
|
||||||
|
senderName?: string,
|
||||||
|
receiverName?: string,
|
||||||
|
) => Promise<void>;
|
||||||
addChat: (userId: string, name: string) => void;
|
addChat: (userId: string, name: string) => void;
|
||||||
addMessage: (userId: string, message: ChatMessage) => void;
|
addMessage: (userId: string, message: ChatMessage) => void;
|
||||||
removeChat: (userId: string) => void;
|
removeChat: (userId: string) => void;
|
||||||
@@ -49,12 +56,13 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
|
|||||||
},
|
},
|
||||||
setOwnId: (id: string) => set({ ownId: id }),
|
setOwnId: (id: string) => set({ ownId: id }),
|
||||||
chats: {},
|
chats: {},
|
||||||
sendMessage: (userId: string, message: string) => {
|
sendMessage: (userId, message) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (dispatchSocket.connected) {
|
if (dispatchSocket.connected) {
|
||||||
|
const zone = useDispatchConnectionStore.getState().selectedZone;
|
||||||
dispatchSocket.emit(
|
dispatchSocket.emit(
|
||||||
"send-message",
|
"send-message",
|
||||||
{ userId, message },
|
{ userId, message, role: zone },
|
||||||
({ error }: { error?: string }) => {
|
({ error }: { error?: string }) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
@@ -64,13 +72,19 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (pilotSocket.connected) {
|
} else if (pilotSocket.connected) {
|
||||||
pilotSocket.emit("send-message", { userId, message }, ({ error }: { error?: string }) => {
|
const bosCallsign = usePilotConnectionStore.getState().selectedStation?.bosCallsignShort;
|
||||||
if (error) {
|
|
||||||
reject(error);
|
pilotSocket.emit(
|
||||||
} else {
|
"send-message",
|
||||||
resolve();
|
{ userId, message, role: bosCallsign },
|
||||||
}
|
({ error }: { error?: string }) => {
|
||||||
});
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,12 +30,17 @@ export const penaltyColumns: ColumnDef<Penalty & { Report: Report; CreatedUser:
|
|||||||
new Date(row.original.until || Date.now()),
|
new Date(row.original.until || Date.now()),
|
||||||
{ locale: de },
|
{ locale: de },
|
||||||
);
|
);
|
||||||
|
const isExpired = new Date(row.original.until || Date.now()) < new Date();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("text-warning flex gap-3", row.original.suspended && "text-gray-400")}
|
className={cn(
|
||||||
|
"text-warning flex gap-3",
|
||||||
|
(row.original.suspended || isExpired) && "text-gray-400",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Timer />
|
<Timer />
|
||||||
Zeit Sperre ({length}) {row.original.suspended && "(ausgesetzt)"}
|
Zeit Sperre ({length}) {row.original.suspended && "(ausgesetzt)"}{" "}
|
||||||
|
{isExpired && !row.original.suspended && "(abgelaufen)"}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -78,14 +83,14 @@ export const penaltyColumns: ColumnDef<Penalty & { Report: Report; CreatedUser:
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Link href={`/admin/penalty/${row.original.id}`}>
|
<Link href={`/admin/penalty/${row.original.id}`}>
|
||||||
<button className="btn btn-sm btn-outline btn-info flex items-center gap-2">
|
<button className="btn btn-sm btn-outline btn-info flex items-center gap-2">
|
||||||
<Shield className="w-4 h-4" />
|
<Shield className="h-4 w-4" />
|
||||||
Anzeigen
|
Anzeigen
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
{report && (
|
{report && (
|
||||||
<Link href={`/admin/report/${report.id}`}>
|
<Link href={`/admin/report/${report.id}`}>
|
||||||
<button className="btn btn-sm btn-outliney flex items-center gap-2">
|
<button className="btn btn-sm btn-outliney flex items-center gap-2">
|
||||||
<TriangleAlert className="w-4 h-4" />
|
<TriangleAlert className="h-4 w-4" />
|
||||||
Report Anzeigen
|
Report Anzeigen
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ export default function ReportPage() {
|
|||||||
CreatedUser: true,
|
CreatedUser: true,
|
||||||
Report: true,
|
Report: true,
|
||||||
}}
|
}}
|
||||||
|
initialOrderBy={[
|
||||||
|
{
|
||||||
|
id: "timestamp",
|
||||||
|
desc: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
columns={penaltyColumns}
|
columns={penaltyColumns}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const NewReportForm = ({
|
|||||||
reviewerUserId: null,
|
reviewerUserId: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(form.formState.errors);
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="flex flex-wrap gap-3"
|
className="flex flex-wrap gap-3"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
ConnectedAircraft,
|
ConnectedAircraft,
|
||||||
ConnectedDispatcher,
|
ConnectedDispatcher,
|
||||||
DiscordAccount,
|
DiscordAccount,
|
||||||
|
Penalty,
|
||||||
PERMISSION,
|
PERMISSION,
|
||||||
Station,
|
Station,
|
||||||
User,
|
User,
|
||||||
@@ -46,6 +47,7 @@ import {
|
|||||||
ShieldUser,
|
ShieldUser,
|
||||||
Timer,
|
Timer,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
TriangleAlert,
|
||||||
Users,
|
Users,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -255,6 +257,7 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }: ProfileFormPro
|
|||||||
|
|
||||||
export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: User }) => {
|
export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: User }) => {
|
||||||
const dispoTableRef = useRef<PaginatedTableRef>(null);
|
const dispoTableRef = useRef<PaginatedTableRef>(null);
|
||||||
|
const pilotTableRef = useRef<PaginatedTableRef>(null);
|
||||||
return (
|
return (
|
||||||
<div className="card-body flex-row flex-wrap">
|
<div className="card-body flex-row flex-wrap">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -302,7 +305,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<Button
|
||||||
className="btn btn-sm btn-error"
|
className="btn btn-sm btn-error"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await deleteDispoHistory(row.original.id);
|
await deleteDispoHistory(row.original.id);
|
||||||
@@ -310,7 +313,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
löschen
|
löschen
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -324,7 +327,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
|
|||||||
<PlaneIcon className="h-5 w-5" /> Pilot-Verbindungs Historie
|
<PlaneIcon className="h-5 w-5" /> Pilot-Verbindungs Historie
|
||||||
</h2>
|
</h2>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
ref={dispoTableRef}
|
ref={pilotTableRef}
|
||||||
filter={{
|
filter={{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}}
|
}}
|
||||||
@@ -375,15 +378,15 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<Button
|
||||||
className="btn btn-sm btn-error"
|
className="btn btn-sm btn-error"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await deletePilotHistory(row.original.id);
|
await deletePilotHistory(row.original.id);
|
||||||
dispoTableRef.current?.refresh();
|
pilotTableRef.current?.refresh();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
löschen
|
löschen
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -408,6 +411,7 @@ export const UserPenalties = ({ user }: { user: User }) => {
|
|||||||
</span>
|
</span>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<PenaltyDropdown
|
<PenaltyDropdown
|
||||||
|
showBtnName
|
||||||
btnName="Zeitstrafe hinzufügen"
|
btnName="Zeitstrafe hinzufügen"
|
||||||
Icon={<Timer size={15} />}
|
Icon={<Timer size={15} />}
|
||||||
onClick={async ({ reason, until }) => {
|
onClick={async ({ reason, until }) => {
|
||||||
@@ -437,6 +441,7 @@ export const UserPenalties = ({ user }: { user: User }) => {
|
|||||||
/>
|
/>
|
||||||
{session.data?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
|
{session.data?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
|
||||||
<PenaltyDropdown
|
<PenaltyDropdown
|
||||||
|
showBtnName
|
||||||
btnName="Bannen"
|
btnName="Bannen"
|
||||||
Icon={<LockKeyhole size={15} />}
|
Icon={<LockKeyhole size={15} />}
|
||||||
onClick={async ({ reason }) => {
|
onClick={async ({ reason }) => {
|
||||||
@@ -528,6 +533,12 @@ interface AdminFormProps {
|
|||||||
open: number;
|
open: number;
|
||||||
total60Days: number;
|
total60Days: number;
|
||||||
};
|
};
|
||||||
|
openBans: (Penalty & {
|
||||||
|
CreatedUser: User | null;
|
||||||
|
})[];
|
||||||
|
openTimebans: (Penalty & {
|
||||||
|
CreatedUser: User | null;
|
||||||
|
})[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdminForm = ({
|
export const AdminForm = ({
|
||||||
@@ -536,6 +547,8 @@ export const AdminForm = ({
|
|||||||
pilotTime,
|
pilotTime,
|
||||||
reports,
|
reports,
|
||||||
discordAccount,
|
discordAccount,
|
||||||
|
openBans,
|
||||||
|
openTimebans,
|
||||||
}: AdminFormProps) => {
|
}: AdminFormProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
@@ -627,6 +640,33 @@ export const AdminForm = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{(!!openBans.length || !!openTimebans.length) && (
|
||||||
|
<div role="alert" className="alert alert-warning alert-outline flex flex-col">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<TriangleAlert />
|
||||||
|
{openBans.map((ban) => (
|
||||||
|
<div key={ban.id}>
|
||||||
|
<h3 className="text-lg font-semibold">Permanent ausgeschlossen</h3>
|
||||||
|
{ban.reason} (von {ban.CreatedUser?.firstname} {ban.CreatedUser?.lastname} -{" "}
|
||||||
|
{ban.CreatedUser?.publicId})
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{openTimebans.map((timeban) => (
|
||||||
|
<div key={timeban.id}>
|
||||||
|
<h3 className="text-lg font-semibold">
|
||||||
|
Zeitstrafe bis{" "}
|
||||||
|
{timeban.until ? new Date(timeban.until).toLocaleString("de-DE") : "unbekannt"}
|
||||||
|
</h3>
|
||||||
|
{timeban.reason} ({timeban.CreatedUser?.firstname} {timeban.CreatedUser?.lastname} -{" "}
|
||||||
|
{timeban.CreatedUser?.publicId})
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-400">
|
||||||
|
Achtung! Die Strafe(n) sind aktiv, die Rechte des Nutzers müssen nicht angepasst werden!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<ChartBarBigIcon className="h-5 w-5" /> Aktivität
|
<ChartBarBigIcon className="h-5 w-5" /> Aktivität
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import {
|
|||||||
UserReports,
|
UserReports,
|
||||||
} from "./_components/forms";
|
} from "./_components/forms";
|
||||||
import { Error } from "../../../../_components/Error";
|
import { Error } from "../../../../_components/Error";
|
||||||
|
import { getUserPenaltys } from "@repo/shared-components";
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: id,
|
id: id,
|
||||||
@@ -20,6 +20,7 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
|||||||
discordAccounts: true,
|
discordAccounts: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (!user) return <Error statusCode={404} title="User not found" />;
|
||||||
|
|
||||||
const dispoSessions = await prisma.connectedDispatcher.findMany({
|
const dispoSessions = await prisma.connectedDispatcher.findMany({
|
||||||
where: {
|
where: {
|
||||||
@@ -97,41 +98,43 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
|||||||
open: totalReportsOpen,
|
open: totalReportsOpen,
|
||||||
total60Days: totalReports60Days,
|
total60Days: totalReports60Days,
|
||||||
};
|
};
|
||||||
if (!user) return <Error statusCode={404} title="User not found" />;
|
const { openBans, openTimeban } = await getUserPenaltys(user?.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-6 gap-4">
|
<div className="grid grid-cols-6 gap-4">
|
||||||
<div className="col-span-full flex justify-between items-center">
|
<div className="col-span-full flex items-center justify-between">
|
||||||
<p className="text-2xl font-semibold text-left flex items-center gap-2">
|
<p className="flex items-center gap-2 text-left text-2xl font-semibold">
|
||||||
<PersonIcon className="w-5 h-5" />
|
<PersonIcon className="h-5 w-5" />
|
||||||
{user?.firstname} {user?.lastname} #{user?.publicId}
|
{user?.firstname} {user?.lastname} #{user?.publicId}
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
className="text-sm text-gray-400 font-thin tooltip tooltip-left"
|
className="tooltip tooltip-left text-sm font-thin text-gray-400"
|
||||||
data-tip="Account erstellt am"
|
data-tip="Account erstellt am"
|
||||||
>
|
>
|
||||||
{new Date(user.createdAt).toLocaleString("de-DE")}
|
{new Date(user.createdAt).toLocaleString("de-DE")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
<div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-3">
|
||||||
<ProfileForm user={user} />
|
<ProfileForm user={user} />
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
<div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-3">
|
||||||
<AdminForm
|
<AdminForm
|
||||||
user={user}
|
user={user}
|
||||||
dispoTime={dispoTime}
|
dispoTime={dispoTime}
|
||||||
pilotTime={pilotTime}
|
pilotTime={pilotTime}
|
||||||
reports={reports}
|
reports={reports}
|
||||||
discordAccount={user.discordAccounts[0]}
|
discordAccount={user.discordAccounts[0]}
|
||||||
|
openBans={openBans}
|
||||||
|
openTimebans={openTimeban}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-6">
|
<div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-6">
|
||||||
<UserReports user={user} />
|
<UserReports user={user} />
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-6">
|
<div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-6">
|
||||||
<UserPenalties user={user} />
|
<UserPenalties user={user} />
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-6">
|
<div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-6">
|
||||||
<ConnectionHistory user={user} />
|
<ConnectionHistory user={user} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const ChangelogModal = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-base-content/80 mb-2 mt-4 text-left">
|
<div className="text-base-content/80 mb-2 mt-4 text-left" data-color-mode="dark">
|
||||||
<MDEditor.Markdown
|
<MDEditor.Markdown
|
||||||
source={latestChangelog.text}
|
source={latestChangelog.text}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ export const PenaltyDropdown = ({
|
|||||||
btnTip,
|
btnTip,
|
||||||
btnName,
|
btnName,
|
||||||
Icon,
|
Icon,
|
||||||
|
showBtnName = false,
|
||||||
}: {
|
}: {
|
||||||
onClick: (data: { reason: string; until: Date | null }) => void;
|
onClick: (data: { reason: string; until: Date | null }) => void;
|
||||||
showDatePicker?: boolean;
|
showDatePicker?: boolean;
|
||||||
btnClassName?: string;
|
btnClassName?: string;
|
||||||
btnName: string;
|
btnName: string;
|
||||||
btnTip?: string;
|
btnTip?: string;
|
||||||
|
showBtnName?: boolean;
|
||||||
Icon: ReactNode;
|
Icon: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -25,25 +27,29 @@ export const PenaltyDropdown = ({
|
|||||||
<div tabIndex={0} role="button"></div>
|
<div tabIndex={0} role="button"></div>
|
||||||
<div className="indicator">
|
<div className="indicator">
|
||||||
<button
|
<button
|
||||||
className={cn("btn btn-xs btn-square btn-soft cursor-pointer", btnClassName)}
|
className={cn(
|
||||||
|
"btn btn-xs btn-soft cursor-pointer",
|
||||||
|
!showBtnName && "btn-square",
|
||||||
|
btnClassName,
|
||||||
|
)}
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
>
|
>
|
||||||
{Icon}
|
{Icon} {showBtnName && <span className="hidden md:inline-block">{btnName}</span>}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{open && (
|
{open && (
|
||||||
<div
|
<div
|
||||||
className="dropdown-content bg-base-100 rounded-box z-1 p-4 shadow-sm space-y-4 shadow-md"
|
className="dropdown-content bg-base-100 rounded-box z-1 space-y-4 p-4 shadow-md shadow-sm"
|
||||||
style={{ minWidth: "500px", right: "40px" }}
|
style={{ minWidth: "500px", right: "40px" }}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="absolute top-2 right-2 btn btn-xs btn-circle btn-ghost"
|
className="btn btn-xs btn-circle btn-ghost absolute right-2 top-2"
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span className="text-xl leading-none">×</span>
|
<span className="text-xl leading-none">×</span>
|
||||||
</button>
|
</button>
|
||||||
<h2 className="text-xl font-bold text-center">{btnName}</h2>
|
<h2 className="text-center text-xl font-bold">{btnName}</h2>
|
||||||
<textarea
|
<textarea
|
||||||
value={reason}
|
value={reason}
|
||||||
onChange={(e) => setReason(e.target.value)}
|
onChange={(e) => setReason(e.target.value)}
|
||||||
@@ -53,7 +59,7 @@ export const PenaltyDropdown = ({
|
|||||||
/>
|
/>
|
||||||
{showDatePicker && (
|
{showDatePicker && (
|
||||||
<select
|
<select
|
||||||
className="select w-full select-bordered"
|
className="select select-bordered w-full"
|
||||||
value={until}
|
value={until}
|
||||||
onChange={(e) => setUntil(e.target.value)}
|
onChange={(e) => setUntil(e.target.value)}
|
||||||
>
|
>
|
||||||
@@ -74,7 +80,7 @@ export const PenaltyDropdown = ({
|
|||||||
</select>
|
</select>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className={cn("btn w-full btn-square btn-soft tooltip tooltip-bottom", btnClassName)}
|
className={cn("btn btn-square btn-soft tooltip tooltip-bottom w-full", btnClassName)}
|
||||||
data-tip={btnTip}
|
data-tip={btnTip}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let untilDate: Date | null = null;
|
let untilDate: Date | null = null;
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export * from "./dates";
|
|||||||
export * from "./simulatorConnected";
|
export * from "./simulatorConnected";
|
||||||
export * from "./useDebounce";
|
export * from "./useDebounce";
|
||||||
export * from "./useTimeout";
|
export * from "./useTimeout";
|
||||||
|
export * from "./penaltys";
|
||||||
|
|||||||
32
packages/shared-components/helper/penaltys.ts
Normal file
32
packages/shared-components/helper/penaltys.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { prisma } from "@repo/db";
|
||||||
|
|
||||||
|
export const getUserPenaltys = async (userId: string) => {
|
||||||
|
const openTimeban = await prisma.penalty.findMany({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
until: {
|
||||||
|
gte: new Date(),
|
||||||
|
},
|
||||||
|
suspended: false,
|
||||||
|
type: "TIME_BAN",
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
CreatedUser: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const openBans = await prisma.penalty.findMany({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
suspended: false,
|
||||||
|
type: "BAN",
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
CreatedUser: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
openTimeban,
|
||||||
|
openBans,
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user