Changed error boundary to cover both full hub and disaptch app

This commit is contained in:
PxlLoewe
2025-05-29 22:25:30 -07:00
parent d968507484
commit 0cebe2b97e
24 changed files with 226 additions and 194 deletions

View File

@@ -6,12 +6,11 @@ import { eventCompleted } from "../../../helper/events";
const page = async () => {
const session = await getServerSession();
if (!session) return null;
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
});
const user = session.user;
if (!user) return null;
const events = await prisma.event.findMany({

View File

@@ -7,10 +7,7 @@ import { PlaneIcon } from "lucide-react";
export const PilotStats = async () => {
const session = await getServerSession();
if (!session) return null;
const user = await prisma.user.findUnique({
where: { id: session.user.id },
});
const user = session.user;
const mostFlownStationsIds = await prisma.missionOnStationUsers.groupBy({
where: {
userId: user?.id,
@@ -63,8 +60,7 @@ export const PilotStats = async () => {
orderBy: { _count: { userId: "desc" } },
});
const ownRankMissionsFlown =
missionsFlownRanks.findIndex((rank) => rank.userId === user?.id) + 1;
const ownRankMissionsFlown = missionsFlownRanks.findIndex((rank) => rank.userId === user?.id) + 1;
const totalUserCount = await prisma.user.count({
where: {
@@ -108,8 +104,7 @@ export const PilotStats = async () => {
<div className="stat-title">Einsätze geflogen</div>
<div className="stat-value text-primary">{totalFlownMissions}</div>
<div className="stat-desc">
Du bist damit unter den top{" "}
{((ownRankMissionsFlown * 100) / totalUserCount).toFixed(0)}%!
Du bist damit unter den top {((ownRankMissionsFlown * 100) / totalUserCount).toFixed(0)}%!
</div>
</div>
@@ -140,17 +135,12 @@ export const PilotStats = async () => {
<div className="stat-figure text-info">
<PlaneIcon className="w-8 h-8" />
</div>
<div className="stat-value text-info">
{mostFlownStation?.bosCallsign}
</div>
<div className="stat-title">
War bisher dein Rettungsmittel der Wahl
</div>
<div className="stat-value text-info">{mostFlownStation?.bosCallsign}</div>
<div className="stat-title">War bisher dein Rettungsmittel der Wahl</div>
{unflownStationsCount > 0 && (
<div className="stat-desc text-secondary">
{unflownStationsCount}{" "}
{unflownStationsCount > 1 ? "Stationen" : "Station"} warten noch
auf dich!
{unflownStationsCount} {unflownStationsCount > 1 ? "Stationen" : "Station"} warten
noch auf dich!
</div>
)}
{unflownStationsCount === 0 && (
@@ -167,9 +157,7 @@ export const PilotStats = async () => {
export const DispoStats = async () => {
const session = await getServerSession();
if (!session) return null;
const user = await prisma.user.findUnique({
where: { id: session.user.id },
});
const user = session.user;
const dispoSessions = await prisma.connectedDispatcher.findMany({
where: {
@@ -275,9 +263,7 @@ export const DispoStats = async () => {
<div className="stat-figure text-info">
<PlaneIcon className="w-8 h-8" />
</div>
<div className="stat-value text-info">
{mostDispatchedStation?.bosCallsign}
</div>
<div className="stat-value text-info">{mostDispatchedStation?.bosCallsign}</div>
<div className="stat-title">Wurde von dir am meisten Disponiert</div>
<div className="stat-desc text-secondary">
{mostDispatchedStationIds[0]?._count.missionStationIds} Einsätze

View File

@@ -1,20 +1,18 @@
import { prisma } from "@repo/db";
import { Error } from "_components/Error";
import { getServerSession } from "api/auth/[...nextauth]/auth";
export default async ({ children }: { children: React.ReactNode }) => {
const AdminEventLayout = async ({ children }: { children: React.ReactNode }) => {
const session = await getServerSession();
if (!session) return <Error title="Nicht eingeloggt" statusCode={401} />;
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
});
const user = session.user;
if (!user?.permissions.includes("ADMIN_EVENT"))
return <Error title="Keine Berechtigung" statusCode={403} />;
return <>{children}</>;
};
AdminEventLayout.displayName = "AdminEventLayout";
export default AdminEventLayout;

View File

@@ -1,20 +1,19 @@
import { prisma } from "@repo/db";
import { Error } from "_components/Error";
import { getServerSession } from "api/auth/[...nextauth]/auth";
export default async ({ children }: { children: React.ReactNode }) => {
const AdminKeywordLayout = async ({ children }: { children: React.ReactNode }) => {
const session = await getServerSession();
if (!session) return <Error title="Nicht eingeloggt" statusCode={401} />;
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
});
const user = session.user;
if (!user?.permissions.includes("ADMIN_KEYWORD"))
return <Error title="Keine Berechtigung" statusCode={403} />;
return <>{children}</>;
};
AdminKeywordLayout.displayName = "AdminKeywordLayout";
export default AdminKeywordLayout;

View File

@@ -1,21 +1,12 @@
import { prisma } from "@repo/db";
import { Error } from "_components/Error";
import { getServerSession } from "api/auth/[...nextauth]/auth";
export default async function ReportLayout({
children,
}: {
children: React.ReactNode;
}) {
export default async function ReportLayout({ children }: { children: React.ReactNode }) {
const session = await getServerSession();
if (!session) return <Error title="Nicht eingeloggt" statusCode={401} />;
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
});
const user = session.user;
if (!user?.permissions.includes("ADMIN_EVENT"))
return <Error title="Keine Berechtigung" statusCode={403} />;

View File

@@ -1,20 +1,19 @@
import { prisma } from "@repo/db";
import { Error } from "_components/Error";
import { getServerSession } from "api/auth/[...nextauth]/auth";
export default async ({ children }: { children: React.ReactNode }) => {
const AdminStationLayout = async ({ children }: { children: React.ReactNode }) => {
const session = await getServerSession();
if (!session) return <Error title="Nicht eingeloggt" statusCode={401} />;
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
});
const user = session.user;
if (!user?.permissions.includes("ADMIN_STATION"))
return <Error title="Keine Berechtigung" statusCode={403} />;
return <>{children}</>;
};
AdminStationLayout.displayName = "AdminStationLayout";
export default AdminStationLayout;

View File

@@ -11,12 +11,7 @@ import {
} from "@repo/db";
import { useRef, useState } from "react";
import { useForm } from "react-hook-form";
import {
deleteDispoHistory,
deletePilotHistory,
editUser,
resetPassword,
} from "../../action";
import { deleteDispoHistory, deletePilotHistory, editUser, resetPassword } from "../../action";
import { toast } from "react-hot-toast";
import {
PersonIcon,
@@ -42,9 +37,7 @@ interface ProfileFormProps {
user: User;
}
export const ProfileForm: React.FC<ProfileFormProps> = ({
user,
}: ProfileFormProps) => {
export const ProfileForm: React.FC<ProfileFormProps> = ({ user }: ProfileFormProps) => {
const [isLoading, setIsLoading] = useState(false);
const form = useForm<User>({
defaultValues: user,
@@ -83,9 +76,7 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
/>
</label>
{form.formState.errors.firstname && (
<p className="text-error">
{form.formState.errors.firstname.message}
</p>
<p className="text-error">{form.formState.errors.firstname.message}</p>
)}
<label className="floating-label w-full mb-5">
<span className="text-lg flex items-center gap-2">
@@ -100,9 +91,7 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
/>
</label>
{form.formState.errors.lastname && (
<p className="text-error">
{form.formState.errors.lastname?.message}
</p>
<p className="text-error">{form.formState.errors.lastname?.message}</p>
)}
<label className="floating-label w-full">
<span className="text-lg flex items-center gap-2">
@@ -154,11 +143,7 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
);
};
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);
return (
<div className="card-body flex-row flex-wrap">
@@ -185,9 +170,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({
accessorKey: "loginTime",
header: "Login",
cell: ({ row }) => {
return new Date(row.getValue("loginTime")).toLocaleString(
"de-DE",
);
return new Date(row.getValue("loginTime")).toLocaleString("de-DE");
},
},
{
@@ -197,9 +180,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({
return <span className="text-success">Online</span>;
}
const loginTime = new Date(row.original.loginTime).getTime();
const logoutTime = new Date(
row.original.logoutTime,
).getTime();
const logoutTime = new Date(row.original.logoutTime).getTime();
const timeOnline = logoutTime - loginTime;
@@ -253,10 +234,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({
header: "Station",
cell: ({ row }) => {
return (
<Link
className="link link-hover"
href={`/admin/station/${row.original.id}`}
>
<Link className="link link-hover" href={`/admin/station/${row.original.id}`}>
{row.original.Station.bosCallsign}
</Link>
);
@@ -266,9 +244,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({
accessorKey: "loginTime",
header: "Login",
cell: ({ row }) => {
return new Date(row.getValue("loginTime")).toLocaleString(
"de-DE",
);
return new Date(row.getValue("loginTime")).toLocaleString("de-DE");
},
},
{
@@ -278,9 +254,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({
return <span className="text-success">Online</span>;
}
const loginTime = new Date(row.original.loginTime).getTime();
const logoutTime = new Date(
row.original.logoutTime,
).getTime();
const logoutTime = new Date(row.original.logoutTime).getTime();
const timeOnline = logoutTime - loginTime;
const hours = Math.floor(timeOnline / 1000 / 60 / 60);
@@ -360,19 +334,10 @@ export const UserReports = ({ user }: { user: User }) => {
return `${user.firstname} ${user.lastname} (${user.publicId})`;
},
},
{
accessorKey: "Reported",
header: "Reported",
cell: ({ row }) => {
const user = row.getValue("Reported") as User;
return `${user.firstname} ${user.lastname} (${user.publicId})`;
},
},
{
accessorKey: "timestamp",
header: "Time",
cell: ({ row }) =>
new Date(row.getValue("timestamp")).toLocaleString(),
cell: ({ row }) => new Date(row.getValue("timestamp")).toLocaleString(),
},
{
accessorKey: "actions",
@@ -411,12 +376,7 @@ interface AdminFormProps {
};
}
export const AdminForm = ({
user,
dispoTime,
pilotTime,
reports,
}: AdminFormProps) => {
export const AdminForm = ({ user, dispoTime, pilotTime, reports }: AdminFormProps) => {
const router = useRouter();
return (
@@ -495,8 +455,7 @@ export const AdminForm = ({
</div>
<div className="stat-title">Dispo Zeit</div>
<div className="stat-desc text-secondary">
{dispoTime.lastLogin &&
new Date(dispoTime.lastLogin).toLocaleString("de-DE")}
{dispoTime.lastLogin && new Date(dispoTime.lastLogin).toLocaleString("de-DE")}
</div>
</div>
<div className="stat">
@@ -508,8 +467,7 @@ export const AdminForm = ({
</div>
<div className="stat-title">Pilot Zeit</div>
<div className="stat-desc text-secondary">
{pilotTime.lastLogin &&
new Date(pilotTime.lastLogin).toLocaleString("de-DE")}
{pilotTime.lastLogin && new Date(pilotTime.lastLogin).toLocaleString("de-DE")}
</div>
</div>
</div>
@@ -523,12 +481,7 @@ export const AdminForm = ({
<div className="stat-figure text-primary">
<ExclamationTriangleIcon className="w-8 h-8" />
</div>
<div
className={cn(
"stat-value text-primary",
reports.open && "text-warning",
)}
>
<div className={cn("stat-value text-primary", reports.open && "text-warning")}>
{reports.open}
</div>
<div className="stat-title">Offen</div>
@@ -539,9 +492,7 @@ export const AdminForm = ({
</div>
<div className="stat-value text-primary">{reports.total60Days}</div>
<div className="stat-title">in den letzten 60 Tagen</div>
<div className="stat-desc text-secondary">
{reports.total} insgesammt
</div>
<div className="stat-desc text-secondary">{reports.total} insgesammt</div>
</div>
</div>
</div>

View File

@@ -1,20 +1,19 @@
import { prisma } from "@repo/db";
import { Error } from "_components/Error";
import { getServerSession } from "api/auth/[...nextauth]/auth";
export default async ({ children }: { children: React.ReactNode }) => {
const AdminUserLayout = async ({ children }: { children: React.ReactNode }) => {
const session = await getServerSession();
if (!session) return <Error title="Nicht eingeloggt" statusCode={401} />;
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
});
const user = session.user;
if (!user?.permissions.includes("ADMIN_USER"))
return <Error title="Keine Berechtigung" statusCode={403} />;
return <>{children}</>;
};
AdminUserLayout.displayName = "AdminUserLayout";
export default AdminUserLayout;

View File

@@ -6,11 +6,7 @@ import { RocketIcon } from "@radix-ui/react-icons";
const page = async () => {
const session = await getServerSession();
if (!session) return null;
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
});
const user = session.user;
if (!user) return null;
const events = await prisma.event.findMany({