added reports stats for admins
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { BADGES, PERMISSION, User } from "@repo/db";
|
import { BADGES, PERMISSION, Report, User } from "@repo/db";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { deleteDispoHistory, editUser, resetPassword } from "../../action";
|
import { deleteDispoHistory, editUser, resetPassword } from "../../action";
|
||||||
@@ -20,10 +20,10 @@ import { Select } from "../../../../../_components/ui/Select";
|
|||||||
import { UserSchema } from "@repo/db/zod";
|
import { UserSchema } from "@repo/db/zod";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable";
|
import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable";
|
||||||
import { min } from "date-fns";
|
|
||||||
import { cn } from "../../../../../../helper/cn";
|
import { cn } from "../../../../../../helper/cn";
|
||||||
import { ChartBarBigIcon, PlaneIcon } from "lucide-react";
|
import { ChartBarBigIcon, Check, Eye, PlaneIcon, Timer, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
||||||
interface ProfileFormProps {
|
interface ProfileFormProps {
|
||||||
user: User;
|
user: User;
|
||||||
@@ -305,6 +305,79 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const UserReports = ({ user }: { user: User }) => {
|
||||||
|
return (
|
||||||
|
<div className="card-body">
|
||||||
|
<h2 className="card-title">
|
||||||
|
<ExclamationTriangleIcon className="w-5 h-5" /> User Reports
|
||||||
|
</h2>
|
||||||
|
<PaginatedTable
|
||||||
|
prismaModel="report"
|
||||||
|
filter={{
|
||||||
|
reportedUserId: user.id,
|
||||||
|
}}
|
||||||
|
include={{
|
||||||
|
Sender: true,
|
||||||
|
Reported: true,
|
||||||
|
}}
|
||||||
|
columns={
|
||||||
|
[
|
||||||
|
{
|
||||||
|
accessorKey: "reviewed",
|
||||||
|
header: "Erledigt",
|
||||||
|
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="text-center">
|
||||||
|
{row.getValue("reviewed") ? (
|
||||||
|
<Check className="text-green-500 w-5 h-5" />
|
||||||
|
) : (
|
||||||
|
<X className="text-red-500 w-5 h-5" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "Sender",
|
||||||
|
header: "Sender",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const user = row.getValue("Sender") as 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(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "actions",
|
||||||
|
header: "Actions",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Link href={`/admin/report/${row.original.id}`}>
|
||||||
|
<button className="btn btn-sm btn-outline btn-info flex items-center gap-2">
|
||||||
|
<Eye className="w-4 h-4" /> Anzeigen
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] as ColumnDef<Report>[]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface AdminFormProps {
|
interface AdminFormProps {
|
||||||
user: User;
|
user: User;
|
||||||
dispoTime: {
|
dispoTime: {
|
||||||
@@ -317,9 +390,19 @@ interface AdminFormProps {
|
|||||||
minutes: number;
|
minutes: number;
|
||||||
lastLogin?: Date;
|
lastLogin?: Date;
|
||||||
};
|
};
|
||||||
|
reports: {
|
||||||
|
total: number;
|
||||||
|
open: number;
|
||||||
|
total60Days: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdminForm = ({ user, dispoTime, pilotTime }: AdminFormProps) => {
|
export const AdminForm = ({
|
||||||
|
user,
|
||||||
|
dispoTime,
|
||||||
|
pilotTime,
|
||||||
|
reports,
|
||||||
|
}: AdminFormProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -388,7 +471,7 @@ export const AdminForm = ({ user, dispoTime, pilotTime }: AdminFormProps) => {
|
|||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<ChartBarBigIcon className="w-5 h-5" /> Aktivität
|
<ChartBarBigIcon className="w-5 h-5" /> Aktivität
|
||||||
</h2>
|
</h2>
|
||||||
<div className="stats shadow">
|
<div className="stats flex">
|
||||||
<div className="stat">
|
<div className="stat">
|
||||||
<div className="stat-figure text-primary">
|
<div className="stat-figure text-primary">
|
||||||
<LightningBoltIcon className="w-8 h-8" />
|
<LightningBoltIcon className="w-8 h-8" />
|
||||||
@@ -419,7 +502,34 @@ export const AdminForm = ({ user, dispoTime, pilotTime }: AdminFormProps) => {
|
|||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<ExclamationTriangleIcon className="w-5 h-5" /> Reports
|
<ExclamationTriangleIcon className="w-5 h-5" /> Reports
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* TODO: Report summary Here */}
|
{/* TODO: Report summary Here */}
|
||||||
|
<div className="stats flex">
|
||||||
|
<div className="stat">
|
||||||
|
<div className="stat-figure text-primary">
|
||||||
|
<ExclamationTriangleIcon className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"stat-value text-primary",
|
||||||
|
reports.open && "text-warning",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{reports.open}
|
||||||
|
</div>
|
||||||
|
<div className="stat-title">Offen</div>
|
||||||
|
</div>
|
||||||
|
<div className="stat">
|
||||||
|
<div className="stat-figure text-primary">
|
||||||
|
<Timer className="w-8 h-8" />
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { PersonIcon } from "@radix-ui/react-icons";
|
import { PersonIcon } from "@radix-ui/react-icons";
|
||||||
import { prisma, User } from "@repo/db";
|
import { prisma, User } from "@repo/db";
|
||||||
import { AdminForm, ConnectionHistory, ProfileForm } from "./_components/forms";
|
import {
|
||||||
|
AdminForm,
|
||||||
|
ConnectionHistory,
|
||||||
|
ProfileForm,
|
||||||
|
UserReports,
|
||||||
|
} from "./_components/forms";
|
||||||
import { Error } from "../../../../_components/Error";
|
import { Error } from "../../../../_components/Error";
|
||||||
|
|
||||||
const Page = async ({ params }: { params: { id: string } }) => {
|
const Page = async ({ params }: { params: { id: string } }) => {
|
||||||
@@ -62,6 +67,33 @@ const Page = async ({ params }: { params: { id: string } }) => {
|
|||||||
lastLogin: pilotSessions[pilotSessions.length - 1]?.loginTime,
|
lastLogin: pilotSessions[pilotSessions.length - 1]?.loginTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const totalReportsReports = await prisma.report.count({
|
||||||
|
where: {
|
||||||
|
reportedUserId: user?.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const totalReports60Days = await prisma.report.count({
|
||||||
|
where: {
|
||||||
|
reportedUserId: user?.id,
|
||||||
|
timestamp: {
|
||||||
|
gte: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalReportsOpen = await prisma.report.count({
|
||||||
|
where: {
|
||||||
|
reportedUserId: user?.id,
|
||||||
|
reviewed: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const reports = {
|
||||||
|
total: totalReportsReports,
|
||||||
|
open: totalReportsOpen,
|
||||||
|
total60Days: totalReports60Days,
|
||||||
|
};
|
||||||
|
|
||||||
if (!user) return <Error statusCode={404} title="User not found" />;
|
if (!user) return <Error statusCode={404} title="User not found" />;
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-6 gap-4">
|
<div className="grid grid-cols-6 gap-4">
|
||||||
@@ -75,7 +107,15 @@ const Page = async ({ params }: { params: { id: string } }) => {
|
|||||||
<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 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
||||||
<AdminForm user={user} dispoTime={dispoTime} pilotTime={pilotTime} />
|
<AdminForm
|
||||||
|
user={user}
|
||||||
|
dispoTime={dispoTime}
|
||||||
|
pilotTime={pilotTime}
|
||||||
|
reports={reports}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-6">
|
||||||
|
<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 shadow-xl mb-4 col-span-6 xl:col-span-6">
|
||||||
<ConnectionHistory user={user} />
|
<ConnectionHistory user={user} />
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user